torch-molecule 0.1.0.dev2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. torch_molecule/__init__.py +70 -0
  2. torch_molecule/base/__init__.py +8 -0
  3. torch_molecule/base/base.py +396 -0
  4. torch_molecule/base/encoder.py +44 -0
  5. torch_molecule/base/generator.py +24 -0
  6. torch_molecule/base/old_encoder.py +322 -0
  7. torch_molecule/base/old_predictor.py +904 -0
  8. torch_molecule/base/old_predictor2.py +436 -0
  9. torch_molecule/base/predictor.py +112 -0
  10. torch_molecule/encoder/attrmask/__init__.py +3 -0
  11. torch_molecule/encoder/attrmask/model.py +113 -0
  12. torch_molecule/encoder/attrmask/modeling_attrmask.py +304 -0
  13. torch_molecule/encoder/constant.py +13 -0
  14. torch_molecule/encoder/contextpred/__init__.py +3 -0
  15. torch_molecule/encoder/contextpred/model.py +207 -0
  16. torch_molecule/encoder/contextpred/modeling_contextpred.py +299 -0
  17. torch_molecule/encoder/contextpred/utils.py +206 -0
  18. torch_molecule/encoder/edgepred/__init__.py +3 -0
  19. torch_molecule/encoder/edgepred/model.py +104 -0
  20. torch_molecule/encoder/edgepred/modeling_edgepred.py +285 -0
  21. torch_molecule/encoder/graphmae/__init__.py +3 -0
  22. torch_molecule/encoder/graphmae/dataloader.py +206 -0
  23. torch_molecule/encoder/graphmae/model.py +137 -0
  24. torch_molecule/encoder/graphmae/modeling_graphmae.py +323 -0
  25. torch_molecule/encoder/infograph/__init__.py +3 -0
  26. torch_molecule/encoder/infograph/loss_function.py +128 -0
  27. torch_molecule/encoder/infograph/model.py +92 -0
  28. torch_molecule/encoder/infograph/modeling_infograph.py +301 -0
  29. torch_molecule/encoder/infograph/module.py +31 -0
  30. torch_molecule/encoder/moama/__init__.py +3 -0
  31. torch_molecule/encoder/moama/model.py +131 -0
  32. torch_molecule/encoder/moama/modeling_moama.py +307 -0
  33. torch_molecule/encoder/moama/utils.py +143 -0
  34. torch_molecule/encoder/pretrained/__init__.py +3 -0
  35. torch_molecule/encoder/pretrained/modeling_pretrained.py +274 -0
  36. torch_molecule/encoder/supervised/__init__.py +3 -0
  37. torch_molecule/encoder/supervised/model.py +97 -0
  38. torch_molecule/encoder/supervised/modeling_supervised.py +354 -0
  39. torch_molecule/generator/digress/__init__.py +3 -0
  40. torch_molecule/generator/digress/diffusion.py +194 -0
  41. torch_molecule/generator/digress/modeling_digress.py +503 -0
  42. torch_molecule/generator/digress/transformer.py +360 -0
  43. torch_molecule/generator/digress/utils.py +196 -0
  44. torch_molecule/generator/gdss/__init__.py +3 -0
  45. torch_molecule/generator/gdss/layers.py +257 -0
  46. torch_molecule/generator/gdss/model.py +204 -0
  47. torch_molecule/generator/gdss/modeling_gdss.py +474 -0
  48. torch_molecule/generator/gdss/sde.py +334 -0
  49. torch_molecule/generator/gdss/solver.py +227 -0
  50. torch_molecule/generator/gdss/toremove/ScoreNetworkA.py +167 -0
  51. torch_molecule/generator/gdss/toremove/ScoreNetworkX.py +85 -0
  52. torch_molecule/generator/gdss/utils.py +240 -0
  53. torch_molecule/generator/graph_dit/__init__.py +3 -0
  54. torch_molecule/generator/graph_dit/diffusion.py +188 -0
  55. torch_molecule/generator/graph_dit/modeling_graph_dit.py +581 -0
  56. torch_molecule/generator/graph_dit/transformer.py +207 -0
  57. torch_molecule/generator/graph_dit/utils.py +220 -0
  58. torch_molecule/generator/graph_ga/__init__.py +3 -0
  59. torch_molecule/generator/graph_ga/crossover.py +182 -0
  60. torch_molecule/generator/graph_ga/modeling_graph_ga.py +310 -0
  61. torch_molecule/generator/graph_ga/mutate.py +136 -0
  62. torch_molecule/generator/graph_ga/oracle.py +95 -0
  63. torch_molecule/generator/jtvae/__init__.py +3 -0
  64. torch_molecule/generator/jtvae/jtnn/chemutils.py +450 -0
  65. torch_molecule/generator/jtvae/jtnn/datautils.py +127 -0
  66. torch_molecule/generator/jtvae/jtnn/jtmpn.py +140 -0
  67. torch_molecule/generator/jtvae/jtnn/jtnn_dec.py +350 -0
  68. torch_molecule/generator/jtvae/jtnn/jtnn_enc.py +131 -0
  69. torch_molecule/generator/jtvae/jtnn/mol_tree.py +111 -0
  70. torch_molecule/generator/jtvae/jtnn/mpn.py +129 -0
  71. torch_molecule/generator/jtvae/jtnn/nnutils.py +66 -0
  72. torch_molecule/generator/jtvae/jtnn/vocab.py +31 -0
  73. torch_molecule/generator/jtvae/jtnn_vae.py +246 -0
  74. torch_molecule/generator/jtvae/modeling_jtvae.py +247 -0
  75. torch_molecule/generator/lstm/__init__.py +3 -0
  76. torch_molecule/generator/lstm/action_sampler.py +91 -0
  77. torch_molecule/generator/lstm/lstm.py +59 -0
  78. torch_molecule/generator/lstm/modeling_lstm.py +263 -0
  79. torch_molecule/generator/lstm/smiles_char_dict.py +123 -0
  80. torch_molecule/generator/lstm/utils.py +62 -0
  81. torch_molecule/generator/molgpt/__init__.py +3 -0
  82. torch_molecule/generator/molgpt/dataset.py +87 -0
  83. torch_molecule/generator/molgpt/gpt.py +251 -0
  84. torch_molecule/generator/molgpt/modeling_molgpt.py +427 -0
  85. torch_molecule/generator/pretrained/modeling_pretrained.py +1 -0
  86. torch_molecule/nn/__init__.py +16 -0
  87. torch_molecule/nn/attention.py +63 -0
  88. torch_molecule/nn/embedder.py +183 -0
  89. torch_molecule/nn/gnn.py +631 -0
  90. torch_molecule/nn/mlp.py +34 -0
  91. torch_molecule/predictor/bfgnn/__init__.py +3 -0
  92. torch_molecule/predictor/bfgnn/model.py +116 -0
  93. torch_molecule/predictor/bfgnn/modeling_bfgnn.py +81 -0
  94. torch_molecule/predictor/dir/__init__.py +3 -0
  95. torch_molecule/predictor/dir/model.py +235 -0
  96. torch_molecule/predictor/dir/modeling_dir.py +161 -0
  97. torch_molecule/predictor/dir/utils.py +40 -0
  98. torch_molecule/predictor/gnn/__init__.py +3 -0
  99. torch_molecule/predictor/gnn/model.py +114 -0
  100. torch_molecule/predictor/gnn/modeling_gnn.py +706 -0
  101. torch_molecule/predictor/gnn/old_modeling_gnn.py +707 -0
  102. torch_molecule/predictor/grea/__init__.py +3 -0
  103. torch_molecule/predictor/grea/model.py +190 -0
  104. torch_molecule/predictor/grea/modeling_grea.py +107 -0
  105. torch_molecule/predictor/grea/old1_features.py +229 -0
  106. torch_molecule/predictor/grea/old1_modeling_grea.py +493 -0
  107. torch_molecule/predictor/grea/utils.py +97 -0
  108. torch_molecule/predictor/grin/__init__.py +3 -0
  109. torch_molecule/predictor/grin/model.py +116 -0
  110. torch_molecule/predictor/grin/modeling_grin.py +91 -0
  111. torch_molecule/predictor/irm/__init__.py +3 -0
  112. torch_molecule/predictor/irm/model.py +127 -0
  113. torch_molecule/predictor/irm/modeling_irm.py +179 -0
  114. torch_molecule/predictor/lstm/__init__.py +3 -0
  115. torch_molecule/predictor/lstm/model.py +145 -0
  116. torch_molecule/predictor/lstm/modeling_lstm.py +627 -0
  117. torch_molecule/predictor/lstm/token_from_smiles.py +85 -0
  118. torch_molecule/predictor/old_modeling_graph.py +650 -0
  119. torch_molecule/predictor/rpgnn/__init__.py +3 -0
  120. torch_molecule/predictor/rpgnn/model.py +185 -0
  121. torch_molecule/predictor/rpgnn/modeling_rpgnn.py +76 -0
  122. torch_molecule/predictor/sgir/__init__.py +3 -0
  123. torch_molecule/predictor/sgir/modeling_sgir.py +260 -0
  124. torch_molecule/predictor/sgir/strategy.py +266 -0
  125. torch_molecule/predictor/smiles_transformer/__init__.py +3 -0
  126. torch_molecule/predictor/smiles_transformer/model.py +424 -0
  127. torch_molecule/predictor/smiles_transformer/modeling_transformer.py +252 -0
  128. torch_molecule/predictor/ssr/__init__.py +3 -0
  129. torch_molecule/predictor/ssr/model.py +283 -0
  130. torch_molecule/predictor/ssr/modeling_ssr.py +267 -0
  131. torch_molecule/predictor/ssr/utils.py +185 -0
  132. torch_molecule/utils/__init__.py +31 -0
  133. torch_molecule/utils/checker.py +158 -0
  134. torch_molecule/utils/checkpoint.py +294 -0
  135. torch_molecule/utils/format.py +140 -0
  136. torch_molecule/utils/generic/metrics.py +530 -0
  137. torch_molecule/utils/generic/pseudo_tasks.py +11 -0
  138. torch_molecule/utils/generic/weights.py +30 -0
  139. torch_molecule/utils/graph/features.py +178 -0
  140. torch_molecule/utils/graph/graph_from_smiles.py +134 -0
  141. torch_molecule/utils/graph/graph_to_smiles.py +312 -0
  142. torch_molecule/utils/hf.py +232 -0
  143. torch_molecule/utils/search.py +64 -0
  144. torch_molecule-0.1.0.dev2.dist-info/METADATA +243 -0
  145. torch_molecule-0.1.0.dev2.dist-info/RECORD +148 -0
  146. torch_molecule-0.1.0.dev2.dist-info/WHEEL +5 -0
  147. torch_molecule-0.1.0.dev2.dist-info/licenses/LICENSE +21 -0
  148. torch_molecule-0.1.0.dev2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,323 @@
1
+ import numpy as np
2
+ from tqdm import tqdm
3
+ from dataclasses import dataclass, field
4
+ from typing import Optional, Union, Dict, Any, Tuple, List, Literal, Type
5
+
6
+ import torch
7
+ from torch_geometric.loader import DataLoader
8
+ from torch_geometric.data import Data
9
+
10
+ from .model import GNN
11
+ from .dataloader import DataLoaderMaskingPred
12
+ from ..constant import GNN_ENCODER_MODELS, GNN_ENCODER_READOUTS, GNN_ENCODER_PARAMS
13
+ from ...base import BaseMolecularEncoder
14
+ from ...utils import graph_from_smiles
15
+
16
+ ALLOWABLE_ENCODER_MODELS = GNN_ENCODER_MODELS
17
+ ALLOWABLE_ENCODER_READOUTS = GNN_ENCODER_READOUTS
18
+
19
+ @dataclass
20
+ class GraphMAEMolecularEncoder(BaseMolecularEncoder):
21
+ """GraphMAE: Self-Supervised Masked Graph Autoencoders
22
+
23
+ References
24
+ ----------
25
+ - Paper: https://arxiv.org/abs/2205.10803
26
+ - Code: https://github.com/THUDM/GraphMAE/tree/main/chem
27
+
28
+ Parameters
29
+ ----------
30
+ mask_rate : float, default=0.15
31
+ Fraction of nodes to mask during training.
32
+ mask_edge : bool, default=False
33
+ Whether to mask edges in addition to nodes.
34
+ predictor_type : str, default="gin"
35
+ Type of predictor network to use for reconstruction.
36
+ Options: ["gin", "gcn", "linear"]
37
+
38
+ num_layer : int, default=5
39
+ Number of message passing layers in the GNN.
40
+ hidden_size : int, default=300
41
+ Dimension of hidden node representations.
42
+ drop_ratio : float, default=0.5
43
+ Dropout probability.
44
+ norm_layer : str, default="batch_norm"
45
+ Type of normalization to use.
46
+ Options: ["batch_norm", "layer_norm", "instance_norm", "graph_norm", "size_norm", "pair_norm"]
47
+
48
+ encoder_type : str, default="gin-virtual"
49
+ Type of GNN encoder to use.
50
+ Options: ["gin-virtual", "gcn-virtual", "gin", "gcn"]
51
+ readout : str, default="sum"
52
+ Pooling method to use for graph-level representations.
53
+ Options: ["sum", "mean", "max"]
54
+
55
+ batch_size : int, default=128
56
+ Batch size for training and inference.
57
+ epochs : int, default=500
58
+ Number of training epochs.
59
+ learning_rate : float, default=0.001
60
+ Learning rate for optimizer.
61
+ grad_clip_value : Optional[float], default=None
62
+ Maximum norm of gradients for gradient clipping. No clipping if None.
63
+ weight_decay : float, default=0.0
64
+ L2 regularization factor.
65
+
66
+ use_lr_scheduler : bool, default=False
67
+ Whether to use a learning rate scheduler.
68
+ scheduler_factor : float, default=0.5
69
+ Factor by which to reduce learning rate when using scheduler.
70
+ scheduler_patience : int, default=5
71
+ Number of epochs with no improvement after which learning rate will be reduced.
72
+
73
+ verbose : bool, default=False
74
+ Whether to display progress bars and logs.
75
+ model_name : str, default="GraphMAEMolecularEncoder"
76
+ Name of the model.
77
+
78
+ Examples
79
+ --------
80
+ >>> from torch_molecule import GraphMAEMolecularEncoder
81
+ >>> encoder = GraphMAEMolecularEncoder(hidden_size=128, epochs=100)
82
+ >>> encoder.fit(["CC(=O)OC1=CC=CC=C1C(=O)O", "CCO", "C1=CC=CC=C1"])
83
+ >>> representations = encoder.encode(["CCO"])
84
+ """
85
+ # Task related parameters
86
+ mask_rate: float = 0.15
87
+ mask_edge: bool = False # whether to mask edges
88
+ predictor_type: str = "gin" # one of ["gin", "gcn", "linear"]
89
+
90
+ # Model parameters
91
+ num_layer: int = 5
92
+ hidden_size: int = 300
93
+ drop_ratio: float = 0.5
94
+ norm_layer: str = "batch_norm" # one of ["batch_norm", "layer_norm", "instance_norm", "graph_norm", "size_norm", "pair_norm"]
95
+
96
+ encoder_type: str = "gin-virtual" # one of ["gin-virtual", "gcn-virtual", "gin", "gcn"]
97
+ readout: str = "sum" # one of ["sum", "mean", "max"]
98
+
99
+ # Training parameters
100
+ batch_size: int = 128
101
+ epochs: int = 500
102
+ learning_rate: float = 0.001
103
+ grad_clip_value: Optional[float] = None
104
+ weight_decay: float = 0.0
105
+
106
+ # Scheduler parameters
107
+ use_lr_scheduler: bool = False
108
+ scheduler_factor: float = 0.5 # if use_lr_scheduler is True
109
+ scheduler_patience: int = 5 # if use_lr_scheduler is True
110
+
111
+ # Other parameters
112
+ verbose: bool = False
113
+ model_name: str = "GraphMAEMolecularEncoder"
114
+
115
+ # Non-init fields
116
+ fitting_loss: List[float] = field(default_factory=list, init=False)
117
+ fitting_epoch: int = field(default=0, init=False)
118
+ model_class: Type[GNN] = field(default=GNN, init=False)
119
+
120
+ def __post_init__(self):
121
+ """Initialize the model after dataclass initialization."""
122
+ super().__post_init__()
123
+ if self.encoder_type not in ALLOWABLE_ENCODER_MODELS:
124
+ raise ValueError(f"Invalid encoder_model: {self.encoder_type}. Currently only {ALLOWABLE_ENCODER_MODELS} are supported.")
125
+ if self.readout not in ALLOWABLE_ENCODER_READOUTS:
126
+ raise ValueError(f"Invalid encoder_readout: {self.readout}. Currently only {ALLOWABLE_ENCODER_READOUTS} are supported.")
127
+
128
+ @staticmethod
129
+ def _get_param_names() -> List[str]:
130
+ """Get parameter names for the estimator.
131
+
132
+ Returns
133
+ -------
134
+ List[str]
135
+ List of parameter names that can be used for model configuration.
136
+ """
137
+ return ["mask_rate", "mask_edge", "predictor_type"] + GNN_ENCODER_PARAMS.copy()
138
+
139
+ def _get_model_params(self, checkpoint: Optional[Dict] = None) -> Dict[str, Any]:
140
+ params = {
141
+ "num_layer": self.num_layer,
142
+ "hidden_size": self.hidden_size,
143
+ "drop_ratio": self.drop_ratio,
144
+ "norm_layer": self.norm_layer,
145
+ "readout": self.readout,
146
+ "encoder_type": self.encoder_type,
147
+ "predictor_type": self.predictor_type,
148
+ "mask_edge": self.mask_edge
149
+ }
150
+
151
+ if checkpoint is not None:
152
+ if "hyperparameters" not in checkpoint:
153
+ raise ValueError("Checkpoint missing 'hyperparameters' key")
154
+ hyperparameters = checkpoint["hyperparameters"]
155
+ params = {k: hyperparameters.get(k, v) for k, v in params.items()}
156
+
157
+ return params
158
+
159
+ def _convert_to_pytorch_data(self, X):
160
+ """Convert numpy arrays to PyTorch Geometric data format.
161
+ """
162
+ if self.verbose:
163
+ iterator = tqdm(enumerate(X), desc="Converting molecules to graphs", total=len(X))
164
+ else:
165
+ iterator = enumerate(X)
166
+
167
+ pyg_graph_list = []
168
+ for idx, smiles_or_mol in iterator:
169
+ graph = graph_from_smiles(smiles_or_mol, None)
170
+ g = Data()
171
+ # g.num_nodes = graph["num_nodes"]
172
+ g.edge_index = torch.from_numpy(graph["edge_index"])
173
+
174
+ del graph["num_nodes"]
175
+ del graph["edge_index"]
176
+
177
+ if graph["edge_feat"] is not None:
178
+ g.edge_attr = torch.from_numpy(graph["edge_feat"])
179
+ del graph["edge_feat"]
180
+
181
+ if graph["node_feat"] is not None:
182
+ g.x = torch.from_numpy(graph["node_feat"])
183
+ del graph["node_feat"]
184
+
185
+ pyg_graph_list.append(g)
186
+
187
+ return pyg_graph_list
188
+
189
+ def _setup_optimizers(self) -> Tuple[torch.optim.Optimizer, Optional[Any]]:
190
+ """Setup optimization components including optimizer and learning rate scheduler.
191
+ """
192
+ optimizer = torch.optim.Adam(self.model.parameters(), lr=self.learning_rate, weight_decay=self.weight_decay)
193
+ scheduler = None
194
+ if self.use_lr_scheduler:
195
+ scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
196
+ optimizer,
197
+ mode="min",
198
+ factor=self.scheduler_factor,
199
+ patience=self.scheduler_patience,
200
+ min_lr=1e-6,
201
+ cooldown=0,
202
+ eps=1e-8,
203
+ )
204
+
205
+ return optimizer, scheduler
206
+
207
+ def fit(
208
+ self,
209
+ X_train: List[str],
210
+ ) -> "GraphMAEMolecularEncoder":
211
+ """Fit the model to the training data with optional validation set.
212
+
213
+ Parameters
214
+ ----------
215
+ X_train : List[str]
216
+ Training set input molecular structures as SMILES strings
217
+ Returns
218
+ -------
219
+ self : GraphMAEMolecularEncoder
220
+ Fitted estimator
221
+ """
222
+
223
+ self._initialize_model(self.model_class)
224
+ self.model.initialize_parameters()
225
+ optimizer, scheduler = self._setup_optimizers()
226
+
227
+ # Prepare datasets and loaders
228
+ X_train, _ = self._validate_inputs(X_train, return_rdkit_mol=True)
229
+ train_dataset = self._convert_to_pytorch_data(X_train)
230
+
231
+ train_loader = DataLoaderMaskingPred(
232
+ train_dataset,
233
+ batch_size=self.batch_size,
234
+ shuffle=True, num_workers = 0,
235
+ mask_rate=self.mask_rate,
236
+ mask_edge=self.mask_edge)
237
+
238
+ self.fitting_loss = []
239
+
240
+ for epoch in range(self.epochs):
241
+ # Training phase
242
+ train_losses = self._train_epoch(train_loader, optimizer, epoch)
243
+ self.fitting_loss.append(np.mean(train_losses))
244
+ if scheduler:
245
+ scheduler.step(np.mean(train_losses))
246
+
247
+ self.fitting_epoch = epoch
248
+ self.is_fitted_ = True
249
+ return self
250
+
251
+ def _train_epoch(self, train_loader, optimizer, epoch):
252
+ """Training logic for one epoch.
253
+
254
+ Args:
255
+ train_loader: DataLoader containing training data
256
+ optimizer: Optimizer instance for model parameter updates
257
+
258
+ Returns:
259
+ list: List of loss values for each training step
260
+ """
261
+ self.model.train()
262
+ losses = []
263
+
264
+ iterator = (
265
+ tqdm(train_loader, desc="Training", leave=False)
266
+ if self.verbose
267
+ else train_loader
268
+ )
269
+
270
+ for batch in iterator:
271
+ batch = batch.to(self.device)
272
+ optimizer.zero_grad()
273
+ loss_atom, loss_edge = self.model.compute_loss(batch)
274
+ loss = loss_atom + loss_edge
275
+ loss.backward()
276
+ if self.grad_clip_value is not None:
277
+ torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_value)
278
+ optimizer.step()
279
+ losses.append(loss.item())
280
+
281
+ if self.verbose:
282
+ iterator.set_postfix({"Epoch": f"{epoch}", "Loss": f"{loss.item():.4f}", "Loss_atom": f"{loss_atom.item():.4f}", "Loss_edge": f"{loss_edge.item():.4f}"})
283
+
284
+ return losses
285
+
286
+ def encode(self, X: List[str], return_type: Literal["np", "pt"] = "pt") -> Union[np.ndarray, torch.Tensor]:
287
+ """Encode molecules into vector representations.
288
+
289
+ Parameters
290
+ ----------
291
+ X : List[str]
292
+ List of SMILES strings
293
+ return_type : Literal["np", "pt"], default="pt"
294
+ Return type of the representations
295
+
296
+ Returns
297
+ -------
298
+ representations : ndarray or torch.Tensor
299
+ Molecular representations
300
+ """
301
+ self._check_is_fitted()
302
+
303
+ # Convert to PyTorch Geometric format and create loader
304
+ X, _ = self._validate_inputs(X, return_rdkit_mol=True)
305
+ dataset = self._convert_to_pytorch_data(X)
306
+ loader = DataLoader(dataset, batch_size=self.batch_size, shuffle=False)
307
+
308
+ if self.model is None:
309
+ raise RuntimeError("Model not initialized")
310
+
311
+ # Generate encodings
312
+ self.model = self.model.to(self.device)
313
+ self.model.eval()
314
+ encodings = []
315
+ with torch.no_grad():
316
+ for batch in tqdm(loader, disable=not self.verbose):
317
+ batch = batch.to(self.device)
318
+ out = self.model(batch)
319
+ encodings.append(out["graph"].cpu())
320
+
321
+ # Concatenate and convert to requested format
322
+ encodings = torch.cat(encodings, dim=0)
323
+ return encodings if return_type == "pt" else encodings.numpy()
@@ -0,0 +1,3 @@
1
+ from .modeling_infograph import InfoGraphMolecularEncoder
2
+
3
+ __all__ = ['InfoGraphMolecularEncoder']
@@ -0,0 +1,128 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ import math
5
+
6
+ def local_global_loss_(l_enc, g_enc, batch, measure):
7
+ '''
8
+ Args:
9
+ l: Local feature map.
10
+ g: Global features.
11
+ measure: Type of f-divergence. For use with mode `fd`
12
+ Returns:
13
+ torch.Tensor: Loss.
14
+ '''
15
+ num_graphs = g_enc.shape[0]
16
+ num_nodes = l_enc.shape[0]
17
+
18
+ pos_mask = torch.zeros((num_nodes, num_graphs)).cuda()
19
+ neg_mask = torch.ones((num_nodes, num_graphs)).cuda()
20
+ for nodeidx, graphidx in enumerate(batch):
21
+ pos_mask[nodeidx][graphidx] = 1.
22
+ neg_mask[nodeidx][graphidx] = 0.
23
+
24
+ res = torch.mm(l_enc, g_enc.t())
25
+
26
+ E_pos = get_positive_expectation(res * pos_mask, measure, average=False).sum()
27
+ E_pos = E_pos / num_nodes
28
+ E_neg = get_negative_expectation(res * neg_mask, measure, average=False).sum()
29
+ E_neg = E_neg / (num_nodes * (num_graphs - 1))
30
+
31
+ return E_neg - E_pos
32
+
33
+ def raise_measure_error(measure):
34
+ supported_measures = ['GAN', 'JSD', 'X2', 'KL', 'RKL', 'DV', 'H2', 'W1']
35
+ raise NotImplementedError(
36
+ 'Measure `{}` not supported. Supported: {}'.format(measure,
37
+ supported_measures))
38
+
39
+ def get_positive_expectation(p_samples, measure, average=True):
40
+ """Computes the positive part of a divergence / difference.
41
+
42
+ Args:
43
+ p_samples: Positive samples.
44
+ measure: Measure to compute for.
45
+ average: Average the result over samples.
46
+
47
+ Returns:
48
+ torch.Tensor
49
+
50
+ """
51
+ log_2 = math.log(2.)
52
+
53
+ if measure == 'GAN':
54
+ Ep = - F.softplus(-p_samples)
55
+ elif measure == 'JSD':
56
+ Ep = log_2 - F.softplus(- p_samples)
57
+ elif measure == 'X2':
58
+ Ep = p_samples ** 2
59
+ elif measure == 'KL':
60
+ Ep = p_samples + 1.
61
+ elif measure == 'RKL':
62
+ Ep = -torch.exp(-p_samples)
63
+ elif measure == 'DV':
64
+ Ep = p_samples
65
+ elif measure == 'H2':
66
+ Ep = 1. - torch.exp(-p_samples)
67
+ elif measure == 'W1':
68
+ Ep = p_samples
69
+ else:
70
+ raise_measure_error(measure)
71
+
72
+ if average:
73
+ return Ep.mean()
74
+ else:
75
+ return Ep
76
+
77
+ def get_negative_expectation(q_samples, measure, average=True):
78
+ """Computes the negative part of a divergence / difference.
79
+
80
+ Args:
81
+ q_samples: Negative samples.
82
+ measure: Measure to compute for.
83
+ average: Average the result over samples.
84
+
85
+ Returns:
86
+ torch.Tensor
87
+
88
+ """
89
+ log_2 = math.log(2.)
90
+
91
+ if measure == 'GAN':
92
+ Eq = F.softplus(-q_samples) + q_samples
93
+ elif measure == 'JSD':
94
+ Eq = F.softplus(-q_samples) + q_samples - log_2
95
+ elif measure == 'X2':
96
+ Eq = -0.5 * ((torch.sqrt(q_samples ** 2) + 1.) ** 2)
97
+ elif measure == 'KL':
98
+ Eq = torch.exp(q_samples)
99
+ elif measure == 'RKL':
100
+ Eq = q_samples - 1.
101
+ elif measure == 'DV':
102
+ Eq = log_sum_exp(q_samples, 0) - math.log(q_samples.size(0))
103
+ elif measure == 'H2':
104
+ Eq = torch.exp(q_samples) - 1.
105
+ elif measure == 'W1':
106
+ Eq = q_samples
107
+ else:
108
+ raise_measure_error(measure)
109
+
110
+ if average:
111
+ return Eq.mean()
112
+ else:
113
+ return Eq
114
+
115
+ def log_sum_exp(x, axis=None):
116
+ """Log sum exp function
117
+
118
+ Args:
119
+ x: Input.
120
+ axis: Axis over which to perform sum.
121
+
122
+ Returns:
123
+ torch.Tensor: log sum exp
124
+
125
+ """
126
+ x_max = torch.max(x, axis)[0]
127
+ y = torch.log((torch.exp(x - x_max)).sum(axis)) + x_max
128
+ return y
@@ -0,0 +1,92 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ from torch_geometric.nn import global_add_pool, global_mean_pool, global_max_pool
4
+
5
+ from ...nn import GNN_node, GNN_node_Virtualnode
6
+
7
+ from .module import PriorDiscriminator, FF
8
+ from .loss_function import local_global_loss_
9
+
10
+ class GNN(nn.Module):
11
+ def __init__(
12
+ self,
13
+ num_layer,
14
+ embedding_dim,
15
+ drop_ratio=0.5,
16
+ norm_layer="batch_norm",
17
+ encoder_type="gin-virtual",
18
+ readout="add",
19
+ use_prior=False,
20
+ ):
21
+ super(GNN, self).__init__()
22
+ gnn_name = encoder_type.split("-")[0]
23
+
24
+ self.hidden_size = embedding_dim // num_layer
25
+ self.embedding_dim = embedding_dim
26
+
27
+ encoder_params = {
28
+ "num_layer": num_layer,
29
+ "hidden_size": self.hidden_size,
30
+ "JK": "last",
31
+ "drop_ratio": drop_ratio,
32
+ "residual": False,
33
+ "gnn_name": gnn_name,
34
+ "norm_layer": norm_layer
35
+ }
36
+
37
+ # Choose encoder type based on encoder_model
38
+ encoder_class = GNN_node_Virtualnode if "virtual" in encoder_type else GNN_node
39
+ self.graph_encoder = encoder_class(**encoder_params)
40
+ pooling_funcs = {
41
+ "sum": global_add_pool,
42
+ "mean": global_mean_pool,
43
+ "max": global_max_pool
44
+ }
45
+ self.pool = pooling_funcs.get(readout)
46
+ if self.pool is None:
47
+ raise ValueError(f"Invalid graph pooling type {readout}.")
48
+
49
+ self.local_d = FF(self.embedding_dim)
50
+ self.global_d = FF(self.embedding_dim)
51
+ self.use_prior = use_prior
52
+ if self.use_prior:
53
+ self.prior_d = PriorDiscriminator(self.embedding_dim)
54
+
55
+ self.initialize_parameters()
56
+
57
+ def initialize_parameters(self):
58
+ for m in self.modules():
59
+ if isinstance(m, nn.Linear):
60
+ torch.nn.init.xavier_uniform_(m.weight.data)
61
+ if m.bias is not None:
62
+ m.bias.data.fill_(0.0)
63
+
64
+ def compute_loss(self, batched_data, lw_prior=0.1):
65
+ _, h_node_list = self.graph_encoder(batched_data)
66
+ h_node_list = h_node_list[1:]
67
+ hpool = [self.pool(h_node, batched_data.batch) for h_node in h_node_list]
68
+ h_g = torch.cat(hpool, dim=1)
69
+ h_l = torch.cat(h_node_list, dim=1)
70
+
71
+ g_enc = self.global_d(h_g)
72
+ l_enc = self.local_d(h_l)
73
+
74
+ local_global_loss = local_global_loss_(l_enc, g_enc, batched_data.batch, "JSD")
75
+
76
+ if self.use_prior:
77
+ prior = torch.rand_like(h_g)
78
+ term_a = torch.log(self.prior_d(prior)).mean()
79
+ term_b = torch.log(1.0 - self.prior_d(h_g)).mean()
80
+ PRIOR = - (term_a + term_b) * lw_prior
81
+ else:
82
+ PRIOR = torch.tensor(0.0)
83
+
84
+ return local_global_loss, PRIOR
85
+
86
+ def forward(self, batched_data):
87
+ _, h_node_list = self.graph_encoder(batched_data)
88
+ h_node_list = h_node_list[1:]
89
+ hpool = [self.pool(h_node, batched_data.batch) for h_node in h_node_list]
90
+ h_g = torch.cat(hpool, dim=1)
91
+
92
+ return {"graph": h_g}