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,304 @@
1
+ import os
2
+ import numpy as np
3
+ from tqdm import tqdm
4
+ from typing import Optional, Union, Dict, Any, Tuple, List, Callable, Literal, Type
5
+ import warnings
6
+ from dataclasses import dataclass, field
7
+
8
+ import torch
9
+ from torch_geometric.loader import DataLoader
10
+ from torch_geometric.data import Data
11
+
12
+ from .model import GNN
13
+ from ..constant import GNN_ENCODER_MODELS, GNN_ENCODER_READOUTS, GNN_ENCODER_PARAMS
14
+ from ...base import BaseMolecularEncoder
15
+ from ...utils import graph_from_smiles
16
+
17
+ ALLOWABLE_ENCODER_MODELS = GNN_ENCODER_MODELS
18
+ ALLOWABLE_ENCODER_READOUTS = GNN_ENCODER_READOUTS
19
+
20
+ @dataclass
21
+ class AttrMaskMolecularEncoder(BaseMolecularEncoder):
22
+ """This encoder implements a GNN-based model for molecular representation learning
23
+ using the attribute masking pretraining strategy.
24
+
25
+ References
26
+ ----------
27
+ - Paper: Strategies for Pre-training Graph Neural Networks (ICLR 2020) https://arxiv.org/abs/1905.12265
28
+ - Code: https://github.com/snap-stanford/pretrain-gnns/tree/master/chem
29
+
30
+ Parameters
31
+ ----------
32
+ mask_num : int, default=0
33
+ Number of atom features to mask during pretraining. If set to 0, masking is determined by `mask_rate`.
34
+ mask_rate : float, default=0.15
35
+ Proportion of atoms to mask randomly. Ignored if `mask_num` is set.
36
+ num_layer : int, default=5
37
+ Number of GNN layers.
38
+ hidden_size : int, default=300
39
+ Dimension of hidden node features.
40
+ drop_ratio : float, default=0.5
41
+ Dropout probability.
42
+ norm_layer : str, default="batch_norm"
43
+ Type of normalization layer to use. One of ["batch_norm", "layer_norm", "instance_norm", "graph_norm", "size_norm", "pair_norm"].
44
+ encoder_type : str, default="gin-virtual"
45
+ Type of GNN architecture to use. One of ["gin-virtual", "gcn-virtual", "gin", "gcn"].
46
+ readout : str, default="sum"
47
+ Method for aggregating node features to obtain graph-level representations. One of ["sum", "mean", "max"].
48
+ batch_size : int, default=128
49
+ Number of samples per batch for training.
50
+ epochs : int, default=500
51
+ Maximum number of training epochs.
52
+ learning_rate : float, default=0.001
53
+ Learning rate for optimizer.
54
+ grad_clip_value : float, optional
55
+ Maximum norm of gradients for gradient clipping.
56
+ weight_decay : float, default=0.0
57
+ L2 regularization strength.
58
+ use_lr_scheduler : bool, default=False
59
+ Whether to use a learning rate scheduler.
60
+ scheduler_factor : float, default=0.5
61
+ Factor by which to reduce the learning rate when plateau is detected.
62
+ scheduler_patience : int, default=5
63
+ Number of epochs with no improvement after which learning rate will be reduced.
64
+ verbose : bool, default=False
65
+ Whether to print progress information during training.
66
+ model_name : str, default="AttrMaskMolecularEncoder"
67
+ Name of the encoder model.
68
+ """
69
+ # Task related parameters
70
+ mask_num: int = 0
71
+ mask_rate: float = 0.15
72
+
73
+ # Model parameters
74
+ num_layer: int = 5
75
+ hidden_size: int = 300
76
+ drop_ratio: float = 0.5
77
+ norm_layer: str = "batch_norm"
78
+
79
+ encoder_type: str = "gin-virtual"
80
+ readout: str = "sum"
81
+
82
+ # Training parameters
83
+ batch_size: int = 128
84
+ epochs: int = 500
85
+ learning_rate: float = 0.001
86
+ grad_clip_value: Optional[float] = None
87
+ weight_decay: float = 0.0
88
+
89
+ # Scheduler parameters
90
+ use_lr_scheduler: bool = False
91
+ scheduler_factor: float = 0.5
92
+ scheduler_patience: int = 5
93
+
94
+ # Other parameters
95
+ verbose: bool = False
96
+ model_name: str = "AttrMaskMolecularEncoder"
97
+
98
+ # Non-init fields
99
+ fitting_loss: List[float] = field(default_factory=list, init=False)
100
+ fitting_epoch: int = field(default=0, init=False)
101
+ model_class: Type[GNN] = field(default=GNN, init=False)
102
+
103
+ def __post_init__(self):
104
+ """Initialize the model after dataclass initialization."""
105
+ super().__post_init__()
106
+ if self.encoder_type not in ALLOWABLE_ENCODER_MODELS:
107
+ raise ValueError(f"Invalid encoder_model: {self.encoder_type}. Currently only {ALLOWABLE_ENCODER_MODELS} are supported.")
108
+ if self.readout not in ALLOWABLE_ENCODER_READOUTS:
109
+ raise ValueError(f"Invalid encoder_readout: {self.readout}. Currently only {ALLOWABLE_ENCODER_READOUTS} are supported.")
110
+
111
+ @staticmethod
112
+ def _get_param_names() -> List[str]:
113
+ """Get parameter names for the estimator.
114
+
115
+ Returns
116
+ -------
117
+ List[str]
118
+ List of parameter names that can be used for model configuration.
119
+ """
120
+ return ["mask_num", "mask_rate"] + GNN_ENCODER_PARAMS.copy()
121
+
122
+ def _get_model_params(self, checkpoint: Optional[Dict] = None) -> Dict[str, Any]:
123
+ params = {
124
+ "num_layer": self.num_layer,
125
+ "hidden_size": self.hidden_size,
126
+ "drop_ratio": self.drop_ratio,
127
+ "norm_layer": self.norm_layer,
128
+ "readout": self.readout,
129
+ "encoder_type": self.encoder_type,
130
+ "mask_num": self.mask_num,
131
+ "mask_rate": self.mask_rate
132
+ }
133
+
134
+ if checkpoint is not None:
135
+ if "hyperparameters" not in checkpoint:
136
+ raise ValueError("Checkpoint missing 'hyperparameters' key")
137
+ hyperparameters = checkpoint["hyperparameters"]
138
+ params = {k: hyperparameters.get(k, v) for k, v in params.items()}
139
+
140
+ return params
141
+
142
+ def _convert_to_pytorch_data(self, X):
143
+ """Convert numpy arrays to PyTorch Geometric data format.
144
+ """
145
+ if self.verbose:
146
+ iterator = tqdm(enumerate(X), desc="Converting molecules to graphs", total=len(X))
147
+ else:
148
+ iterator = enumerate(X)
149
+
150
+ pyg_graph_list = []
151
+ for idx, smiles_or_mol in iterator:
152
+ graph = graph_from_smiles(smiles_or_mol, None)
153
+ g = Data()
154
+ g.num_nodes = graph["num_nodes"]
155
+ g.edge_index = torch.from_numpy(graph["edge_index"])
156
+
157
+ del graph["num_nodes"]
158
+ del graph["edge_index"]
159
+
160
+ if graph["edge_feat"] is not None:
161
+ g.edge_attr = torch.from_numpy(graph["edge_feat"])
162
+ del graph["edge_feat"]
163
+
164
+ if graph["node_feat"] is not None:
165
+ g.x = torch.from_numpy(graph["node_feat"])
166
+ del graph["node_feat"]
167
+
168
+ pyg_graph_list.append(g)
169
+
170
+ return pyg_graph_list
171
+
172
+ def _setup_optimizers(self) -> Tuple[torch.optim.Optimizer, Optional[Any]]:
173
+ """Setup optimization components including optimizer and learning rate scheduler.
174
+ """
175
+
176
+ optimizer = torch.optim.Adam(self.model.parameters(), lr=self.learning_rate, weight_decay=self.weight_decay)
177
+ scheduler = None
178
+ if self.use_lr_scheduler:
179
+ scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
180
+ optimizer,
181
+ mode="min",
182
+ factor=self.scheduler_factor,
183
+ patience=self.scheduler_patience,
184
+ min_lr=1e-6,
185
+ cooldown=0,
186
+ eps=1e-8,
187
+ )
188
+
189
+ return optimizer, scheduler
190
+
191
+ def fit(
192
+ self,
193
+ X_train: List[str],
194
+ ) -> "AttrMaskMolecularEncoder":
195
+ """Fit the model to the training data with optional validation set.
196
+
197
+ Parameters
198
+ ----------
199
+ X_train : List[str]
200
+ Training set input molecular structures as SMILES strings
201
+ Returns
202
+ -------
203
+ self : AttrMaskMolecularEncoder
204
+ Fitted estimator
205
+ """
206
+
207
+ self._initialize_model(self.model_class)
208
+ self.model.initialize_parameters()
209
+ optimizer, scheduler = self._setup_optimizers()
210
+
211
+ # Prepare datasets and loaders
212
+ X_train, _ = self._validate_inputs(X_train, return_rdkit_mol=True)
213
+ train_dataset = self._convert_to_pytorch_data(X_train)
214
+ train_loader = DataLoader(
215
+ train_dataset,
216
+ batch_size=self.batch_size,
217
+ shuffle=True,
218
+ num_workers=0
219
+ )
220
+ self.fitting_loss = []
221
+
222
+ for epoch in range(self.epochs):
223
+ # Training phase
224
+ train_losses = self._train_epoch(train_loader, optimizer, epoch)
225
+ self.fitting_loss.append(np.mean(train_losses))
226
+ if scheduler:
227
+ scheduler.step(np.mean(train_losses))
228
+
229
+ self.fitting_epoch = epoch
230
+ self.is_fitted_ = True
231
+ return self
232
+
233
+ def _train_epoch(self, train_loader, optimizer, epoch):
234
+ """Training logic for one epoch.
235
+
236
+ Args:
237
+ train_loader: DataLoader containing training data
238
+ optimizer: Optimizer instance for model parameter updates
239
+
240
+ Returns:
241
+ list: List of loss values for each training step
242
+ """
243
+ self.model.train()
244
+ losses = []
245
+
246
+ iterator = (
247
+ tqdm(train_loader, desc="Training", leave=False)
248
+ if self.verbose
249
+ else train_loader
250
+ )
251
+
252
+ for batch in iterator:
253
+ batch = batch.to(self.device)
254
+ optimizer.zero_grad()
255
+ loss = self.model.compute_loss(batch)
256
+ loss.backward()
257
+ if self.grad_clip_value is not None:
258
+ torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_value)
259
+ optimizer.step()
260
+ losses.append(loss.item())
261
+
262
+ if self.verbose:
263
+ iterator.set_postfix({"Epoch": f"{epoch}", "Loss": f"{loss.item():.4f}"})
264
+
265
+ return losses
266
+
267
+ def encode(self, X: List[str], return_type: Literal["np", "pt"] = "pt") -> Union[np.ndarray, torch.Tensor]:
268
+ """Encode molecules into vector representations.
269
+
270
+ Parameters
271
+ ----------
272
+ X : List[str]
273
+ List of SMILES strings
274
+ return_type : Literal["np", "pt"], default="pt"
275
+ Return type of the representations
276
+
277
+ Returns
278
+ -------
279
+ representations : ndarray or torch.Tensor
280
+ Molecular representations
281
+ """
282
+ self._check_is_fitted()
283
+
284
+ # Convert to PyTorch Geometric format and create loader
285
+ X, _ = self._validate_inputs(X, return_rdkit_mol=True)
286
+ dataset = self._convert_to_pytorch_data(X)
287
+ loader = DataLoader(dataset, batch_size=self.batch_size, shuffle=False)
288
+
289
+ if self.model is None:
290
+ raise RuntimeError("Model not initialized")
291
+
292
+ # Generate encodings
293
+ self.model = self.model.to(self.device)
294
+ self.model.eval()
295
+ encodings = []
296
+ with torch.no_grad():
297
+ for batch in tqdm(loader, disable=not self.verbose):
298
+ batch = batch.to(self.device)
299
+ out = self.model(batch)
300
+ encodings.append(out["graph"].cpu())
301
+
302
+ # Concatenate and convert to requested format
303
+ encodings = torch.cat(encodings, dim=0)
304
+ return encodings if return_type == "pt" else encodings.numpy()
@@ -0,0 +1,13 @@
1
+ GNN_ENCODER_MODELS = ["gin-virtual", "gcn-virtual", "gin", "gcn"]
2
+ GNN_ENCODER_READOUTS = ["sum", "mean", "max"]
3
+
4
+ GNN_ENCODER_PARAMS = [
5
+ # Model Hyperparameters
6
+ "encoder_type", "readout", "num_layer", "hidden_size", "drop_ratio", "norm_layer",
7
+ # Training Parameters
8
+ "batch_size", "epochs", "learning_rate", "weight_decay", "grad_clip_value",
9
+ # Scheduler Parameters
10
+ "use_lr_scheduler", "scheduler_factor", "scheduler_patience",
11
+ # Other Parameters
12
+ "fitting_epoch", "fitting_loss", "device", "verbose", "model_name"
13
+ ]
@@ -0,0 +1,3 @@
1
+ from .modeling_contextpred import ContextPredMolecularEncoder
2
+
3
+ __all__ = ['ContextPredMolecularEncoder']
@@ -0,0 +1,207 @@
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
+ from torch_geometric.data import Data
5
+
6
+ from ...nn import GNN_node, GNN_node_Virtualnode
7
+ from ...utils import init_weights
8
+
9
+ from .utils import ExtractSubstructureContextPair
10
+
11
+ class_criterion = torch.nn.CrossEntropyLoss()
12
+
13
+ def cycle_index(num, shift):
14
+ arr = torch.arange(num) + shift
15
+ arr[-shift:] = torch.arange(shift)
16
+ return arr
17
+
18
+ class GNN(nn.Module):
19
+ def __init__(
20
+ self,
21
+ num_layer,
22
+ hidden_size,
23
+ drop_ratio=0.5,
24
+ norm_layer="batch_norm",
25
+ encoder_type="gin-virtual",
26
+ readout="max",
27
+ mode="cbow",
28
+ context_size=3,
29
+ neg_samples=1
30
+ ):
31
+ super(GNN, self).__init__()
32
+ gnn_name = encoder_type.split("-")[0]
33
+ self.num_layer = num_layer
34
+ self.hidden_size = hidden_size
35
+ self.mode = mode
36
+ self.neg_samples = neg_samples
37
+ self.context_size = context_size
38
+
39
+ encoder_params_substruct = {
40
+ "num_layer": num_layer,
41
+ "hidden_size": hidden_size,
42
+ "JK": "last",
43
+ "drop_ratio": drop_ratio,
44
+ "residual": True,
45
+ "gnn_name": gnn_name,
46
+ "norm_layer": norm_layer
47
+ }
48
+
49
+ encoder_params_context = {
50
+ "num_layer": context_size,
51
+ "hidden_size": hidden_size,
52
+ "JK": "last",
53
+ "drop_ratio": drop_ratio,
54
+ "residual": True,
55
+ "gnn_name": gnn_name,
56
+ "norm_layer": norm_layer
57
+ }
58
+
59
+ # Choose encoder type based on encoder_model
60
+ encoder_class = GNN_node_Virtualnode if "virtual" in encoder_type else GNN_node
61
+ self.graph_encoder_substuct = encoder_class(**encoder_params_substruct)
62
+ pooling_funcs = {
63
+ "sum": global_add_pool,
64
+ "mean": global_mean_pool,
65
+ "max": global_max_pool
66
+ }
67
+ self.pool = pooling_funcs.get(readout)
68
+ if self.pool is None:
69
+ raise ValueError(f"Invalid graph pooling type {readout}.")
70
+
71
+ self.graph_encoder_context = encoder_class(**encoder_params_context)
72
+
73
+ def initialize_parameters(self, seed=None):
74
+ """
75
+ Randomly initialize all model parameters using the init_weights function.
76
+
77
+ Args:
78
+ seed (int, optional): Random seed for reproducibility. Defaults to None.
79
+ """
80
+ if seed is not None:
81
+ torch.manual_seed(seed)
82
+
83
+ # Initialize the main components
84
+ init_weights(self.graph_encoder_substuct)
85
+ init_weights(self.graph_encoder_context)
86
+
87
+ # Reset all parameters using PyTorch Geometric's reset function
88
+ def reset_parameters(module):
89
+ if hasattr(module, 'reset_parameters'):
90
+ module.reset_parameters()
91
+ elif hasattr(module, 'weight') and hasattr(module.weight, 'data'):
92
+ init_weights(module)
93
+
94
+ self.apply(reset_parameters)
95
+
96
+ def compute_loss(self, batched_data):
97
+ device = batched_data.x.device
98
+ extract_context = ExtractSubstructureContextPair(self.num_layer, self.num_layer - 1, self.num_layer + self.context_size - 1)
99
+ keys = ["center_substruct_idx", "edge_attr_substruct", "edge_index_substruct", "x_substruct", "overlap_context_substruct_idx", "edge_attr_context", "edge_index_context", "x_context"]
100
+ for key in keys:
101
+ batched_data[key] = []
102
+
103
+ #used for pooling the substructure
104
+ batched_data.batch_substruct = []
105
+ #used for pooling the context
106
+ batched_data.batch_context = []
107
+ batched_data.batch_overlapped_context = []
108
+ batched_data.overlapped_context_size = []
109
+
110
+ cumsum_main = 0
111
+ cumsum_substruct = 0
112
+ cumsum_context = 0
113
+
114
+ i = 0
115
+
116
+ for j in range(len(batched_data)):
117
+ data = batched_data[j]
118
+ data = extract_context(data)
119
+
120
+ if hasattr(data, "x_context"):
121
+ num_nodes = data.num_nodes
122
+ num_nodes_substruct = len(data.x_substruct)
123
+ num_nodes_context = len(data.x_context)
124
+
125
+ batched_data.batch_substruct.append(torch.full((len(data.substruct_node_idxes), ), i, dtype=torch.long).to(device))
126
+ batched_data.batch_context.append(torch.full((len(data.context_node_idxes), ), i, dtype=torch.long).to(device))
127
+ batched_data.batch_overlapped_context.append(torch.full((len(data.overlap_context_substruct_idx), ), i, dtype=torch.long).to(device))
128
+ batched_data.overlapped_context_size.append(len(data.overlap_context_substruct_idx))
129
+
130
+ ###batching for the substructure graph
131
+ for key in ["center_substruct_idx", "edge_attr_substruct", "edge_index_substruct", "x_substruct"]:
132
+ item = data[key]
133
+ item = item + cumsum_substruct if key in ["edge_index", "edge_index_substruct", "edge_index_context", "overlap_context_substruct_idx", "center_substruct_idx"] else item
134
+ batched_data[key].append(item.to(device))
135
+
136
+
137
+ ###batching for the context graph
138
+ for key in ["overlap_context_substruct_idx", "edge_attr_context", "edge_index_context", "x_context"]:
139
+ item = data[key]
140
+ item = item + cumsum_context if key in ["edge_index", "edge_index_substruct", "edge_index_context", "overlap_context_substruct_idx", "center_substruct_idx"] else item
141
+ batched_data[key].append(item.to(device))
142
+
143
+ cumsum_main += num_nodes
144
+ cumsum_substruct += num_nodes_substruct
145
+ cumsum_context += num_nodes_context
146
+ i += 1
147
+
148
+ for key in keys:
149
+ if key in ["edge_index", "edge_index_substruct", "edge_index_context"]:
150
+ dim = -1
151
+ else:
152
+ dim = 0
153
+ batched_data[key] = torch.cat(batched_data[key], dim=dim)
154
+
155
+ batched_data.batch_substruct = torch.cat(batched_data.batch_substruct, dim=-1).to(device)
156
+ batched_data.batch_context = torch.cat(batched_data.batch_context, dim=-1).to(device)
157
+ batched_data.batch_overlapped_context = torch.cat(batched_data.batch_overlapped_context, dim=-1).to(device)
158
+ batched_data.overlapped_context_size = torch.LongTensor(batched_data.overlapped_context_size).to(device)
159
+
160
+ # generate predictions
161
+ substruct_data = Data(x=batched_data.x_substruct, edge_index=batched_data.edge_index_substruct, edge_attr=batched_data.edge_attr_substruct, batch=batched_data.batch_substruct)
162
+ context_data = Data(x=batched_data.x_context, edge_index=batched_data.edge_index_context, edge_attr=batched_data.edge_attr_context, batch=batched_data.batch_context)
163
+
164
+ substruct_node_rep, _ = self.graph_encoder_substuct(substruct_data)
165
+ #substruct_h_rep = self.pool(substruct_node_rep, batched_data.batch_substruct)
166
+
167
+ context_node_rep, _ = self.graph_encoder_context(context_data)
168
+ context_h_rep = self.pool(context_node_rep, batched_data.batch_context)
169
+
170
+ # contexts are represented by
171
+ if self.mode == "cbow":
172
+ # positive context representation
173
+ context_rep = self.pool(context_node_rep[batched_data.overlap_context_substruct_idx], batched_data.batch_overlapped_context)
174
+ # negative contexts are obtained by shifting the indicies of context embeddings
175
+ neg_context_rep = torch.cat([context_rep[cycle_index(len(context_rep), i+1)] for i in range(self.neg_samples)], dim = 0)
176
+
177
+ pred_pos = torch.sum(substruct_node_rep[batched_data.center_substruct_idx] * context_rep, dim = 1)
178
+ pred_neg = torch.sum(substruct_node_rep[batched_data.center_substruct_idx].repeat((self.neg_samples, 1))*neg_context_rep, dim = 1)
179
+
180
+ elif self.mode == "skipgram":
181
+
182
+ expanded_substruct_rep = torch.cat([substruct_node_rep[batched_data.center_substruct_idx][i].repeat((batched_data.overlapped_context_size[i],1)) for i in range(len(substruct_node_rep[batched_data.center_substruct_idx]))], dim = 0)
183
+ pred_pos = torch.sum(expanded_substruct_rep * context_h_rep, dim = 1)
184
+
185
+ #shift indices of substructures to create negative examples
186
+ shifted_expanded_substruct_rep = []
187
+ for i in range(self.neg_samples):
188
+ shifted_substruct_rep = substruct_node_rep[batched_data.center_substruct_idx][cycle_index(len(substruct_node_rep[batched_data.center_substruct_idx]), i+1)]
189
+ shifted_expanded_substruct_rep.append(torch.cat([shifted_substruct_rep[i].repeat((batched_data.overlapped_context_size[i],1)) for i in range(len(shifted_substruct_rep))], dim = 0))
190
+
191
+ shifted_expanded_substruct_rep = torch.cat(shifted_expanded_substruct_rep, dim = 0)
192
+ pred_neg = torch.sum(shifted_expanded_substruct_rep * context_h_rep.repeat((self.neg_samples, 1)), dim = 1)
193
+
194
+ else:
195
+ raise ValueError("Invalid mode!")
196
+
197
+ loss_pos = class_criterion(pred_pos.double(), torch.ones(len(pred_pos)).to(pred_pos.device).double())
198
+ loss_neg = class_criterion(pred_neg.double(), torch.zeros(len(pred_neg)).to(pred_neg.device).double())
199
+
200
+ loss_class = loss_pos + self.neg_samples*loss_neg
201
+
202
+ return loss_class
203
+
204
+ def forward(self, batched_data):
205
+ h_node, _ = self.graph_encoder_substuct(batched_data)
206
+ h_rep = self.pool(h_node, batched_data.batch)
207
+ return {"graph": h_rep, "node": h_node}