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,185 @@
1
+ import torch
2
+ import torch
3
+ import torch.nn as nn
4
+ from torch_geometric.nn import global_add_pool, global_mean_pool, global_max_pool
5
+ from ...nn import GNN_node, GNN_node_Virtualnode, MLP
6
+ from ...utils import init_weights
7
+
8
+
9
+ class RPGNN(nn.Module):
10
+ def __init__(
11
+ self,
12
+ num_task,
13
+ num_layer,
14
+ num_node_feature,
15
+ fixed_size=32,
16
+ num_perm=3,
17
+ hidden_size=300,
18
+ gnn_type="gin-virtual",
19
+ drop_ratio=0.5,
20
+ norm_layer="batch_norm",
21
+ graph_pooling="mean",
22
+ augmented_feature=['maccs', 'morgan']
23
+ ):
24
+ """
25
+ Random Permutation Graph Neural Network (RPGNN)
26
+
27
+ Args:
28
+ num_task: Number of prediction tasks
29
+ num_layer: Number of GNN layers
30
+ num_node_feature: Number of node features
31
+ fixed_size: Size of position encoding vectors
32
+ num_perm: Number of random permutations to average over
33
+ hidden_size: Hidden dimension size
34
+ gnn_type: Type of GNN to use (gin, gin-virtual, gcn, gcn-virtual)
35
+ drop_ratio: Dropout ratio
36
+ norm_layer: Type of normalization layer
37
+ graph_pooling: Type of graph pooling (mean, sum, max)
38
+ augmented_feature: List of additional molecular features to use
39
+ """
40
+ super(RPGNN, self).__init__()
41
+
42
+ gnn_name = gnn_type.split("-")[0]
43
+ self.num_task = num_task
44
+ self.hidden_size = hidden_size
45
+ self.fixed_size = fixed_size
46
+ self.num_perm = num_perm
47
+ self.augmented_feature = augmented_feature
48
+ self.num_node_feature = num_node_feature
49
+ # Register fixed-size node IDs (position encoding)
50
+ self.register_buffer("node_ids", torch.eye(self.fixed_size))
51
+
52
+ # GNN selection
53
+ if "virtual" in gnn_type:
54
+ self.gnn = GNN_node_Virtualnode(
55
+ num_layer=num_layer,
56
+ hidden_size=hidden_size,
57
+ JK="last",
58
+ drop_ratio=drop_ratio,
59
+ residual=True,
60
+ gnn_name=gnn_name,
61
+ norm_layer=norm_layer
62
+ )
63
+ else:
64
+ self.gnn = GNN_node(
65
+ num_layer=num_layer,
66
+ hidden_size=hidden_size,
67
+ JK="last",
68
+ drop_ratio=drop_ratio,
69
+ residual=True,
70
+ gnn_name=gnn_name,
71
+ norm_layer=norm_layer
72
+ )
73
+
74
+ # Graph pooling selection
75
+ if graph_pooling == "sum":
76
+ self.pool = global_add_pool
77
+ elif graph_pooling == "mean":
78
+ self.pool = global_mean_pool
79
+ elif graph_pooling == "max":
80
+ self.pool = global_max_pool
81
+ else:
82
+ raise ValueError(f"Invalid graph pooling type: {graph_pooling}")
83
+
84
+ # Calculate final prediction dimension
85
+ graph_dim = hidden_size+self.fixed_size
86
+ if augmented_feature:
87
+ if "morgan" in augmented_feature:
88
+ graph_dim += 1024
89
+ if "maccs" in augmented_feature:
90
+ graph_dim += 167
91
+
92
+ # Final predictor MLP
93
+ self.predictor = MLP(
94
+ in_features=graph_dim,
95
+ hidden_features=2 * hidden_size,
96
+ out_features=num_task
97
+ )
98
+
99
+ def initialize_parameters(self, seed=None):
100
+ """Initialize all model parameters."""
101
+ if seed is not None:
102
+ torch.manual_seed(seed)
103
+
104
+ # Initialize GNN and predictor
105
+ init_weights(self.gnn)
106
+ init_weights(self.predictor)
107
+
108
+ # Apply reset_parameters to all modules that have it
109
+ def reset_parameters(module):
110
+ if hasattr(module, 'reset_parameters'):
111
+ module.reset_parameters()
112
+
113
+ self.apply(reset_parameters)
114
+
115
+ def _augmented_graph_features(self, batched_data, h_rep):
116
+ """Add augmented molecular features to the graph representation."""
117
+ if not self.augmented_feature:
118
+ return h_rep
119
+
120
+ if "morgan" in self.augmented_feature:
121
+ morgan = batched_data.morgan.type_as(h_rep)
122
+ h_rep = torch.cat((h_rep, morgan), dim=1)
123
+ if "maccs" in self.augmented_feature:
124
+ maccs = batched_data.maccs.type_as(h_rep)
125
+ h_rep = torch.cat((h_rep, maccs), dim=1)
126
+
127
+ return h_rep
128
+
129
+ def forward(self, batched_data):
130
+ """Forward pass of RPGNN."""
131
+ # Extract original data
132
+ x, edge_index, batch = batched_data.x, batched_data.edge_index, batched_data.batch
133
+
134
+ # Initialize final output
135
+ out = None
136
+
137
+ # Average over multiple permutations
138
+ for _ in range(self.num_perm):
139
+ temp_data = batched_data.clone()
140
+ # Create new_x with same dtype as x
141
+ new_x = torch.empty(x.size(0), x.size(1) + self.fixed_size, dtype=x.dtype, device=x.device)
142
+
143
+ for graph_idx in range(batch.max().item() + 1):
144
+ node_indices = (batch == graph_idx).nonzero().squeeze(1)
145
+ graph_size = node_indices.size(0)
146
+ perm = torch.randperm(graph_size)
147
+
148
+ node_ids = self.node_ids.repeat(
149
+ graph_size // self.fixed_size + 1, 1
150
+ )[:graph_size]
151
+ permuted_node_ids = node_ids[perm, :]
152
+
153
+ # Convert both tensors to the same type as x before concatenation
154
+ new_x[node_indices] = torch.cat([x[node_indices].to(x.dtype), permuted_node_ids.to(x.dtype)], dim=1)
155
+ temp_data.x = new_x[:,:self.num_node_feature]
156
+ # Process through GNN
157
+ h_v, _ = self.gnn(temp_data)
158
+
159
+ fused_h_v = torch.cat([h_v, new_x[:,self.num_node_feature:]], dim=1)
160
+
161
+ # Pool node representations to graph representations
162
+ h_rep = self.pool(fused_h_v, temp_data.batch)
163
+
164
+ # Add augmented features
165
+ h_rep = self._augmented_graph_features(temp_data, h_rep)
166
+
167
+ # Accumulate output
168
+ if out is None:
169
+ out = h_rep / self.num_perm
170
+ else:
171
+ out += h_rep / self.num_perm
172
+
173
+ # Final prediction
174
+ prediction = self.predictor(out)
175
+
176
+ return {"prediction": prediction}
177
+
178
+ def compute_loss(self, batched_data, criterion):
179
+ out = self.forward(batched_data)
180
+ prediction = out['prediction']
181
+ target = batched_data.y.to(torch.float32)
182
+ is_labeled = batched_data.y == batched_data.y
183
+ loss = criterion(prediction.to(torch.float32)[is_labeled], target[is_labeled]).mean()
184
+
185
+ return loss
@@ -0,0 +1,76 @@
1
+ import os
2
+ import numpy as np
3
+ import warnings
4
+ import datetime
5
+ from tqdm import tqdm
6
+ from typing import Optional, Union, Dict, Any, Tuple, List, Callable, Literal, Type
7
+ from dataclasses import dataclass, field
8
+
9
+ import torch
10
+ from torch_geometric.loader import DataLoader
11
+
12
+ from .model import RPGNN
13
+ from ..gnn.modeling_gnn import GNNMolecularPredictor
14
+ from ...utils.search import (
15
+ ParameterSpec,
16
+ ParameterType,
17
+ )
18
+
19
+ @dataclass
20
+ class RPGNNMolecularPredictor(GNNMolecularPredictor):
21
+ """This predictor implements a GNN model based on Relational pooling.
22
+
23
+ The full name of RPGNN is Relational Pooling for Graph Representations.
24
+
25
+ References
26
+ ----------
27
+ - Relational Pooling for Graph Representations.
28
+ https://arxiv.org/abs/1903.02541
29
+ - Reference Code: https://github.com/PurdueMINDS/RelationalPooling/tree/master?tab=readme-ov-file
30
+
31
+ Parameters
32
+ ----------
33
+ num_perm : int, default=3
34
+ Number of random permutations to use for relational pooling.
35
+ fixed_size : int, default=10
36
+ Maximum number of nodes to consider in the graph.
37
+ num_node_feature : int, default=9
38
+ Dimension of the input node features. This should match the number of atomic features used to represent
39
+ each node in the molecular graph (e.g., atomic number, degree, hybridization, etc.).
40
+ """
41
+
42
+ # RPGNN-specific parameter
43
+ num_perm: int = 3
44
+ fixed_size: int = 10
45
+ num_node_feature: int = 9
46
+ # Override parent defaults
47
+ model_name: str = "RPGNNMolecularPredictor"
48
+ model_class: Type[RPGNN] = field(default=RPGNN, init=False)
49
+
50
+ def __post_init__(self):
51
+ super().__post_init__()
52
+
53
+ @staticmethod
54
+ def _get_param_names() -> List[str]:
55
+ return ["num_perm", "fixed_size", "num_node_feature"] + GNNMolecularPredictor._get_param_names()
56
+
57
+ def _get_default_search_space(self):
58
+ search_space = super()._get_default_search_space().copy()
59
+ search_space["num_perm"] = ParameterSpec(ParameterType.INTEGER, (1, 10))
60
+ search_space["fixed_size"] = ParameterSpec(ParameterType.INTEGER, (1, 10))
61
+ return search_space
62
+
63
+ def _get_model_params(self, checkpoint: Optional[Dict] = None) -> Dict[str, Any]:
64
+ base_params = super()._get_model_params(checkpoint)
65
+ if checkpoint and "hyperparameters" in checkpoint:
66
+ base_params["num_perm"] = checkpoint["hyperparameters"]["num_perm"]
67
+ base_params["fixed_size"] = checkpoint["hyperparameters"]["fixed_size"]
68
+ base_params["num_node_feature"] = checkpoint["hyperparameters"]["num_node_feature"]
69
+ else:
70
+ base_params["num_perm"] = self.num_perm
71
+ base_params["fixed_size"] = self.fixed_size
72
+ base_params["num_node_feature"] = self.num_node_feature
73
+ base_params.pop("graph_pooling", None)
74
+ return base_params
75
+
76
+
@@ -0,0 +1,3 @@
1
+ from .modeling_sgir import SGIRMolecularPredictor
2
+
3
+ __all__ = ['SGIRMolecularPredictor']
@@ -0,0 +1,260 @@
1
+ import os
2
+ import numpy as np
3
+ import warnings
4
+ import datetime
5
+ from tqdm import tqdm
6
+ from typing import Optional, Union, Dict, Any, Tuple, List, Callable, Literal
7
+ from dataclasses import dataclass
8
+
9
+ import torch
10
+ from torch_geometric.loader import DataLoader
11
+
12
+ from .strategy import build_selection_dataset, build_augmentation_dataset
13
+ from ..grea.modeling_grea import GREAMolecularPredictor
14
+ from ..grea.model import GREA
15
+
16
+ from ...utils.search import (
17
+ ParameterSpec,
18
+ ParameterType,
19
+ )
20
+
21
+ @dataclass
22
+ class SGIRMolecularPredictor(GREAMolecularPredictor):
23
+ """
24
+ This predictor implements SGIR for semi-supervised graph imbalanced regression.
25
+
26
+ It trains the GREA model based on pseudo-labeling and data augmentation.
27
+
28
+ References
29
+ ----------
30
+ - Semi-Supervised Graph Imbalanced Regression.
31
+ https://dl.acm.org/doi/10.1145/3580305.3599497
32
+ - Code: https://github.com/liugangcode/SGIR
33
+
34
+ :param num_anchor: Number of anchor points used to split the label space during pseudo-labeling
35
+ :type num_anchor: int, default=10
36
+ :param warmup_epoch: Number of epochs to train before starting pseudo-labeling and data augmentation
37
+ :type warmup_epoch: int, default=20
38
+ :param labeling_interval: Interval (in epochs) between pseudo-labeling steps
39
+ :type labeling_interval: int, default=5
40
+ :param augmentation_interval: Interval (in epochs) between data augmentation steps
41
+ :type augmentation_interval: int, default=5
42
+ :param top_quantile: Quantile threshold for selecting high confidence predictions during pseudo-labeling
43
+ :type top_quantile: float, default=0.1
44
+ :param label_logscale: Whether to use log scale for the label space during pseudo-labeling and data augmentation
45
+ :type label_logscale: bool, default=False
46
+ :param lw_aug: Weight for the data augmentation loss
47
+ :type lw_aug: float, default=1
48
+ """
49
+ # SGIR-specific parameters
50
+ num_anchor: int = 10
51
+ warmup_epoch: int = 20
52
+ labeling_interval: int = 5
53
+ augmentation_interval: int = 5
54
+ top_quantile: float = 0.1
55
+ label_logscale: bool = False
56
+ lw_aug: float = 1
57
+ # Override parent defaults
58
+ task_type: str = "regression"
59
+ model_name: str = "SGIRMolecularPredictor"
60
+
61
+ def __post_init__(self):
62
+ super().__post_init__()
63
+
64
+ if self.task_type != "regression" or self.num_task != 1:
65
+ raise ValueError("SGIR only supports regression tasks with 1 task")
66
+
67
+ @staticmethod
68
+ def _get_param_names():
69
+ grea_params = [
70
+ "num_anchor", "warmup_epoch", "labeling_interval",
71
+ "augmentation_interval", "top_quantile", "label_logscale", "lw_aug"
72
+ ]
73
+ return grea_params + GREAMolecularPredictor._get_param_names()
74
+
75
+ def _get_default_search_space(self):
76
+ search_space = super()._get_default_search_space().copy()
77
+ search_space["num_anchor"] = ParameterSpec(ParameterType.INTEGER, (10, 100))
78
+ search_space["labeling_interval"] = ParameterSpec(ParameterType.INTEGER, (10, 20))
79
+ search_space["augmentation_interval"] = ParameterSpec(ParameterType.INTEGER, (10, 20))
80
+ search_space["top_quantile"] = ParameterSpec(ParameterType.LOG_FLOAT, (0.01, 0.5))
81
+ search_space["lw_aug"] = ParameterSpec(ParameterType.FLOAT, (0.1, 1))
82
+ return search_space
83
+
84
+ def fit(
85
+ self,
86
+ X_train: List[str],
87
+ y_train: Optional[Union[List, np.ndarray]],
88
+ X_val: Optional[List[str]] = None,
89
+ y_val: Optional[Union[List, np.ndarray]] = None,
90
+ X_unlbl: Optional[List[str]] = None,
91
+ ) -> "SGIRMolecularPredictor":
92
+ """Fit the model to training data with optional validation set.
93
+ """
94
+ if (X_val is None) != (y_val is None):
95
+ raise ValueError("X_val and y_val must both be provided for validation")
96
+ if X_unlbl is None:
97
+ raise ValueError("X_unlbl (unlabeled SMILES strings) must be provided in SGIR")
98
+ if len(X_unlbl) == 0:
99
+ raise ValueError("X_unlbl (unlabeled SMILES strings) must not be empty")
100
+
101
+ # Initialize model and optimization
102
+ self._initialize_model(self.model_class)
103
+ self.model.initialize_parameters()
104
+ optimizer, scheduler = self._setup_optimizers()
105
+
106
+ # Prepare datasets
107
+ X_train, y_train = self._validate_inputs(X_train, y_train)
108
+ train_dataset = self._convert_to_pytorch_data(X_train, y_train)
109
+ train_loader = DataLoader(
110
+ train_dataset,
111
+ batch_size=self.batch_size,
112
+ shuffle=True,
113
+ num_workers=0
114
+ )
115
+
116
+ X_unlbl, _ = self._validate_inputs(X_unlbl, None)
117
+ unlbl_dataset = self._convert_to_pytorch_data(X_unlbl)
118
+
119
+ if X_val is None:
120
+ val_loader = train_loader
121
+ warnings.warn(
122
+ "No validation set provided. Using training set for validation.",
123
+ UserWarning
124
+ )
125
+ else:
126
+ X_val, y_val = self._validate_inputs(X_val, y_val)
127
+ val_dataset = self._convert_to_pytorch_data(X_val, y_val)
128
+ val_loader = DataLoader(
129
+ val_dataset,
130
+ batch_size=self.batch_size,
131
+ shuffle=False,
132
+ num_workers=0
133
+ )
134
+
135
+ # Training loop
136
+ augmented_dataset = None
137
+ self.fitting_loss = []
138
+ self.fitting_epoch = 0
139
+ best_state_dict = None
140
+ best_eval = float('-inf') if self.evaluate_higher_better else float('inf')
141
+ cnt_wait = 0
142
+
143
+ self.model.train()
144
+ for epoch in range(self.epochs):
145
+ # Training phase
146
+ train_losses = self._train_epoch(train_loader, augmented_dataset, optimizer, epoch)
147
+
148
+ # Update datasets after warmup
149
+ if epoch > self.warmup_epoch:
150
+ if epoch % self.labeling_interval == 0:
151
+ train_loader = build_selection_dataset(
152
+ self.model, train_dataset, unlbl_dataset,
153
+ self.batch_size, self.num_anchor, self.top_quantile,
154
+ self.device, self.label_logscale
155
+ )
156
+
157
+ if epoch % self.augmentation_interval == 0:
158
+ augmented_dataset = build_augmentation_dataset(
159
+ self.model, train_dataset, unlbl_dataset,
160
+ self.batch_size, self.num_anchor, self.device,
161
+ self.label_logscale
162
+ )
163
+
164
+ self.fitting_loss.append(np.mean(train_losses))
165
+
166
+ # Validation and model selection
167
+ current_eval = self._evaluation_epoch(val_loader)
168
+ if scheduler:
169
+ scheduler.step(current_eval)
170
+
171
+ is_better = (
172
+ current_eval > best_eval if self.evaluate_higher_better
173
+ else current_eval < best_eval
174
+ )
175
+
176
+ if is_better:
177
+ self.fitting_epoch = epoch
178
+ best_eval = current_eval
179
+ best_state_dict = self.model.state_dict()
180
+ cnt_wait = 0
181
+ else:
182
+ cnt_wait += 1
183
+ if cnt_wait > self.patience:
184
+ if self.verbose:
185
+ print(f"Early stopping at epoch {epoch}")
186
+ break
187
+
188
+ if self.verbose and epoch % 10 == 0:
189
+ print(
190
+ f"Epoch {epoch}: Loss = {np.mean(train_losses):.4f}, "
191
+ f"{self.evaluate_name} = {current_eval:.4f}, "
192
+ f"Best {self.evaluate_name} = {best_eval:.4f}"
193
+ )
194
+
195
+ # Restore best model
196
+ if best_state_dict is not None:
197
+ self.model.load_state_dict(best_state_dict)
198
+ else:
199
+ warnings.warn(
200
+ "No improvement achieved during training.",
201
+ UserWarning
202
+ )
203
+
204
+ self.is_fitted_ = True
205
+ return self
206
+
207
+ def _train_epoch(self, train_loader, augmented_dataset, optimizer, epoch):
208
+ losses = []
209
+
210
+ if augmented_dataset is not None and self.lw_aug != 0:
211
+ aug_reps = augmented_dataset['representations']
212
+ aug_targets = augmented_dataset['labels']
213
+ random_inds = torch.randperm(aug_reps.size(0))
214
+ aug_reps = aug_reps[random_inds]
215
+ aug_targets = aug_targets[random_inds]
216
+ num_step = len(train_loader)
217
+ aug_batch_size = aug_reps.size(0) // max(1, num_step)
218
+ aug_inputs = list(torch.split(aug_reps, aug_batch_size))
219
+ aug_outputs = list(torch.split(aug_targets, aug_batch_size))
220
+ else:
221
+ aug_inputs = None
222
+ aug_outputs = None
223
+
224
+ iterator = (
225
+ tqdm(train_loader, desc="Training", leave=False)
226
+ if self.verbose
227
+ else train_loader
228
+ )
229
+
230
+ for batch_idx, batch in enumerate(iterator):
231
+ batch = batch.to(self.device)
232
+ optimizer.zero_grad()
233
+
234
+ # augmentation loss
235
+ if aug_inputs is not None and aug_outputs is not None and aug_inputs[batch_idx].size(0) != 1:
236
+ self.model._disable_batchnorm_tracking(self.model)
237
+ pred_aug = self.model.predictor(aug_inputs[batch_idx])
238
+ self.model._enable_batchnorm_tracking(self.model)
239
+ targets_aug = aug_outputs[batch_idx]
240
+ Laug = self.loss_criterion(pred_aug.view(targets_aug.size()).to(torch.float32), targets_aug).mean()
241
+ else:
242
+ Laug = torch.tensor(0.)
243
+ Lx = self.model.compute_loss(batch, self.loss_criterion)
244
+ loss = Lx + Laug * self.lw_aug
245
+
246
+ loss.backward()
247
+
248
+ # Compute gradient norm if gradient clipping is enabled
249
+ if self.grad_clip_value is not None:
250
+ torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_value)
251
+
252
+ optimizer.step()
253
+
254
+ losses.append(loss.item())
255
+
256
+ # Update progress bar if using tqdm
257
+ if self.verbose:
258
+ iterator.set_postfix({"Epoch": epoch, "Total Loss": f"{loss.item():.4f}", "Lbls Loss": f"{Lx.item():.4f}", "Aug Loss": f"{Laug.item():.4f}",})
259
+
260
+ return losses