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,266 @@
1
+ import torch
2
+ from torch_geometric.data import Data
3
+ from torch.distributions.beta import Beta
4
+ from torch_geometric.loader import DataLoader
5
+ from typing import List, Tuple, Dict, Optional
6
+
7
+ def get_sample_probs(counts: torch.Tensor) -> torch.Tensor:
8
+ """Calculate sampling probabilities based on counts."""
9
+ idx = torch.argsort(counts.view(-1), descending=False)
10
+ sample_rate = counts[torch.flipud(idx)] / counts.max()
11
+ probs = torch.zeros_like(sample_rate)
12
+ probs[idx] = sample_rate
13
+ return probs
14
+
15
+ def get_datapoint_list(subset_data: List[Data], new_label_all: Optional[torch.Tensor] = None) -> List[Data]:
16
+ """Convert dataset into list of Data objects with optional new labels."""
17
+ datapoint_list = []
18
+ for idx, datapoint in enumerate(subset_data):
19
+ g = Data(
20
+ edge_index=datapoint.edge_index,
21
+ edge_attr=datapoint.edge_attr,
22
+ x=datapoint.x,
23
+ y=torch.tensor([[new_label_all[idx]]]) if new_label_all is not None else datapoint.y
24
+ )
25
+
26
+ for attr in ['morgan', 'maccs']:
27
+ if hasattr(datapoint, attr):
28
+ setattr(g, attr, getattr(datapoint, attr))
29
+
30
+ datapoint_list.append(g)
31
+ return datapoint_list
32
+
33
+ def mean_by_groups(sample_rep: torch.Tensor, groups: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
34
+ """Calculate mean representations and counts per group."""
35
+ weight = torch.zeros(groups.max() + 1, sample_rep.shape[0], device=sample_rep.device)
36
+ weight[groups, torch.arange(sample_rep.shape[0])] = 1
37
+ group_count = weight.sum(dim=1)
38
+ weight = torch.nn.functional.normalize(weight, p=1, dim=1)
39
+ mean = torch.mm(weight, sample_rep)
40
+ valid_indices = torch.nonzero(group_count > 0).squeeze()
41
+ return mean[valid_indices], group_count[valid_indices]
42
+
43
+ def build_selection_dataset(
44
+ model: torch.nn.Module,
45
+ labeled_dataset: Data,
46
+ unlbl_dataset: Data,
47
+ batch_size: int,
48
+ num_anchor: int,
49
+ threshold: float,
50
+ device: torch.device,
51
+ label_logscale: bool = False
52
+ ) -> DataLoader:
53
+ """Build selection dataset using model predictions and uncertainty."""
54
+ labeled_targets = torch.tensor([g.y for g in labeled_dataset])
55
+ unlabel_idx = torch.arange(len(unlbl_dataset))
56
+
57
+ # Create data loaders
58
+ unlbl_loader = DataLoader(unlbl_dataset, batch_size=batch_size, shuffle=False)
59
+ labeled_loader = DataLoader(labeled_dataset, batch_size=batch_size, shuffle=False)
60
+
61
+ # Get model predictions and uncertainty for unlabeled data
62
+ model.eval()
63
+ unlbl_pred, unlbl_env_vars = [], []
64
+
65
+ with torch.no_grad():
66
+ for batch in unlbl_loader:
67
+ if batch.x.shape[0] <= 1:
68
+ continue
69
+
70
+ batch = batch.to(device)
71
+ output = model(batch)
72
+ pred = output['prediction']
73
+ var = output['variance']
74
+ unlbl_env_vars.append(var.detach())
75
+ unlbl_pred.append(pred.detach())
76
+
77
+ unlbl_pred = torch.cat(unlbl_pred, dim=0).cpu().view(-1)
78
+ unlbl_env_vars = torch.cat(unlbl_env_vars, dim=0).cpu().view(-1)
79
+
80
+ # Sort by uncertainty
81
+ var_asc_idx = torch.argsort(unlbl_env_vars)
82
+ unlbl_env_vars = unlbl_env_vars[var_asc_idx]
83
+ unlbl_pred = unlbl_pred[var_asc_idx]
84
+ unlabel_idx = unlabel_idx[var_asc_idx]
85
+
86
+ # Get uncertainty for labeled data
87
+ labeled_env_vars = []
88
+ with torch.no_grad():
89
+ for batch in labeled_loader:
90
+ if batch.x.shape[0] <= 1:
91
+ continue
92
+
93
+ batch = batch.to(device)
94
+ output = model(batch)
95
+ var = output['variance']
96
+ labeled_env_vars.append(var.detach())
97
+
98
+ labeled_env_vars = torch.cat(labeled_env_vars, dim=0).cpu().view(-1)
99
+
100
+ # Filter by uncertainty threshold
101
+ uncertainty_masks = unlbl_env_vars <= torch.quantile(labeled_env_vars, threshold)
102
+ unlbl_pred = unlbl_pred[uncertainty_masks]
103
+ unlabel_idx = unlabel_idx[uncertainty_masks]
104
+
105
+ # Create label boundaries
106
+ start, end = labeled_targets.min(), labeled_targets.max()
107
+ if label_logscale:
108
+ start, end = torch.log10(start), torch.log10(end)
109
+ boundaries = torch.linspace(start, end, steps=num_anchor + 1)
110
+ if label_logscale:
111
+ boundaries = torch.pow(10, boundaries)
112
+
113
+ # Assign samples to buckets
114
+ bucket_ids = torch.bucketize(labeled_targets.view(-1), boundaries)
115
+ bucket_ids = torch.clamp(bucket_ids, min=1, max=len(boundaries) - 1)
116
+ unique_buckets, bucket_counts = torch.unique(bucket_ids, sorted=True, return_counts=True)
117
+ bucket_centers = (boundaries[unique_buckets - 1] + boundaries[unique_buckets]) / 2
118
+
119
+ # Sample from each bucket
120
+ sampling_probs = get_sample_probs(bucket_counts)
121
+ new_idx_all, new_label_all = [], []
122
+
123
+ for idx, anchor in enumerate(bucket_centers):
124
+ upper_idx = torch.nonzero(boundaries > anchor)[0]
125
+ lower_idx = torch.nonzero(boundaries < anchor)[-1]
126
+ width = boundaries[upper_idx] - boundaries[lower_idx]
127
+
128
+ valid_mask = torch.logical_and(
129
+ unlbl_pred >= anchor - width/2,
130
+ unlbl_pred < anchor + width/2
131
+ )
132
+
133
+ num_picked = min(
134
+ int(valid_mask.sum() * sampling_probs[idx]),
135
+ bucket_counts.max()
136
+ )
137
+
138
+ if num_picked > 0:
139
+ label_dist = torch.abs(anchor - unlbl_pred[valid_mask])
140
+ idx_sorted = torch.argsort(label_dist)[:num_picked]
141
+ new_idx = unlabel_idx[valid_mask][idx_sorted]
142
+ new_idx_all.append(new_idx)
143
+ new_label_all.append(torch.full((num_picked,), anchor))
144
+
145
+ if new_idx_all:
146
+ new_idx_all = torch.cat(new_idx_all)
147
+ new_label_all = torch.cat(new_label_all)
148
+
149
+ # Create final dataset
150
+ new_trainset = get_datapoint_list(labeled_dataset)
151
+ new_unlbl_data = [unlbl_dataset[idx.item()] for idx in new_idx_all]
152
+
153
+ new_trainset.extend(get_datapoint_list(new_unlbl_data, new_label_all))
154
+
155
+ return DataLoader(new_trainset, batch_size=batch_size, shuffle=True)
156
+
157
+ return DataLoader(get_datapoint_list(labeled_dataset), batch_size=batch_size, shuffle=True)
158
+
159
+ def build_augmentation_dataset(
160
+ model: torch.nn.Module,
161
+ labeled_dataset: Data,
162
+ unlbl_dataset: Data,
163
+ batch_size: int,
164
+ num_anchor: int,
165
+ device: torch.device,
166
+ label_logscale: bool = False,
167
+ ) -> Dict[str, torch.Tensor]:
168
+ """Build augmentation dataset using mixup strategy."""
169
+ # Create data loaders
170
+ unlbl_loader = DataLoader(unlbl_dataset, batch_size=batch_size, shuffle=False)
171
+ labeled_loader = DataLoader(labeled_dataset, batch_size=batch_size, shuffle=False)
172
+
173
+ # Get labeled data representations
174
+ model.eval()
175
+ labeled_targets, labeled_reps = [], []
176
+
177
+ with torch.no_grad():
178
+ for batch in labeled_loader:
179
+ if batch.x.shape[0] <= 1:
180
+ continue
181
+
182
+ batch = batch.to(device)
183
+ output = model(batch)
184
+ labeled_targets.append(batch.y.view(output['prediction'].shape))
185
+ labeled_reps.append(output['representation'])
186
+
187
+ labeled_targets = torch.cat(labeled_targets, dim=0)
188
+ labeled_reps = torch.cat(labeled_reps, dim=0)
189
+
190
+ # Get unlabeled data representations
191
+ unlbl_pred, unlabeled_reps = [], []
192
+
193
+ with torch.no_grad():
194
+ for batch in unlbl_loader:
195
+ if batch.x.shape[0] <= 1:
196
+ continue
197
+
198
+ batch = batch.to(device)
199
+ output = model(batch)
200
+ unlbl_pred.append(output['prediction'])
201
+ unlabeled_reps.append(output['representation'])
202
+
203
+ unlbl_pred = torch.cat(unlbl_pred, dim=0)
204
+ unlabeled_reps = torch.cat(unlabeled_reps, dim=0)
205
+
206
+ # Combine all representations
207
+ all_reps = torch.cat([labeled_reps, unlabeled_reps])
208
+ all_labels = torch.cat([labeled_targets, unlbl_pred])
209
+
210
+ # Create label boundaries
211
+ start, end = labeled_targets.min(), labeled_targets.max()
212
+ if label_logscale:
213
+ start, end = torch.log10(start), torch.log10(end)
214
+ boundaries = torch.linspace(start, end, steps=num_anchor + 1)
215
+ if label_logscale:
216
+ boundaries = torch.pow(10, boundaries)
217
+ boundaries = boundaries.to(device)
218
+
219
+ # Assign samples to buckets
220
+ bucket_ids = torch.bucketize(labeled_targets.view(-1), boundaries)
221
+ bucket_ids = torch.clamp(bucket_ids, min=1, max=len(boundaries) - 1)
222
+ unique_buckets = torch.unique(bucket_ids, sorted=True)
223
+ bucket_centers = (boundaries[unique_buckets - 1] + boundaries[unique_buckets]) / 2
224
+
225
+ # Calculate bucket statistics
226
+ bucket_rep, bucket_count = mean_by_groups(labeled_reps, bucket_ids - 1)
227
+ buckets_preds_dist = torch.cdist(bucket_centers.view(-1, 1), all_labels.view(-1, 1), p=2)
228
+ rank_per_buckets = torch.argsort(buckets_preds_dist, dim=1)
229
+
230
+ # Calculate sampling probabilities
231
+ sampling_probs = get_sample_probs(bucket_count)
232
+ samples_per_bucket = torch.clamp(
233
+ torch.ceil(sampling_probs * bucket_count.max()).to(torch.int),
234
+ max=min(rank_per_buckets.size(1), 100)
235
+ )
236
+
237
+ max_samples = samples_per_bucket.max()
238
+ sample_indices = rank_per_buckets[:, :max_samples].contiguous().view(-1)
239
+
240
+ # Perform mixup augmentation
241
+ beta_dist = Beta(torch.tensor([5.]).to(device), torch.tensor([1.]).to(device))
242
+ lambdas = beta_dist.sample((bucket_rep.size(0),)).view(-1, 1)
243
+ lambdas = torch.max(torch.cat([lambdas, 1 - lambdas], dim=1), dim=1).values
244
+
245
+ # Create mixed representations and labels
246
+ mixed_reps = []
247
+ mixed_labels = []
248
+
249
+ for idx, (bucket_r, center, num_samples) in enumerate(zip(bucket_rep, bucket_centers, samples_per_bucket)):
250
+ if num_samples == 0:
251
+ continue
252
+
253
+ lambda_idx = lambdas[idx]
254
+ samples_r = all_reps[sample_indices[idx * max_samples:idx * max_samples + num_samples]]
255
+ samples_l = all_labels[sample_indices[idx * max_samples:idx * max_samples + num_samples]]
256
+
257
+ mixed_r = lambda_idx * bucket_r + (1 - lambda_idx) * samples_r
258
+ mixed_l = lambda_idx * center + (1 - lambda_idx) * samples_l
259
+
260
+ mixed_reps.append(mixed_r)
261
+ mixed_labels.append(mixed_l)
262
+
263
+ return {
264
+ 'representations': torch.cat(mixed_reps),
265
+ 'labels': torch.cat(mixed_labels)
266
+ }
@@ -0,0 +1,3 @@
1
+ from .modeling_transformer import SMILESTransformerMolecularPredictor
2
+
3
+ __all__ = ['SMILESTransformerMolecularPredictor']
@@ -0,0 +1,424 @@
1
+ import math
2
+ import torch
3
+ import numpy as np
4
+ import torch.nn as nn
5
+ import torch.nn.functional as F
6
+ from ...utils import init_weights
7
+
8
+ class Transformer(nn.Module):
9
+ """Transformer-based model for molecular property prediction.
10
+
11
+ Parameters
12
+ ----------
13
+ num_task : int
14
+ Number of prediction tasks.
15
+ input_dim : int
16
+ Size of vocabulary for SMILES tokenization.
17
+ hidden_size : int
18
+ Dimension of hidden layers and embeddings.
19
+ n_heads : int
20
+ Number of attention heads in transformer layers.
21
+ num_layers : int
22
+ Number of transformer encoder layers.
23
+ max_input_len : int
24
+ Maximum length of input sequences.
25
+ dropout : float
26
+ Dropout rate for layers.
27
+ dim_feedforward : int
28
+ Dimension of the feedforward network in transformer layers.
29
+
30
+ Attributes
31
+ ----------
32
+ src_emb : nn.Embedding
33
+ Embedding layer that converts token indices to dense vectors.
34
+ pos_emb : PositionalEncoding
35
+ Positional encoding layer for transformer.
36
+ layers : nn.ModuleList
37
+ List of transformer encoder layers.
38
+ regressor : nn.Sequential
39
+ Final fully connected layers for prediction.
40
+
41
+ Notes
42
+ -----
43
+ The model architecture consists of:
44
+ 1. An embedding layer to convert tokens to vectors
45
+ 2. Positional encoding for transformer
46
+ 3. Multiple transformer encoder layers
47
+ 4. A regression head for prediction
48
+ """
49
+ def __init__(self, num_task, input_dim, hidden_size, n_heads,
50
+ num_layers, max_input_len,
51
+ dropout, dim_feedforward=None):
52
+ super(Transformer, self).__init__()
53
+
54
+ self.num_task = num_task
55
+ self.input_dim = input_dim
56
+ self.hidden_size = hidden_size
57
+ self.n_heads = n_heads
58
+ self.d_k = self.d_v = hidden_size // n_heads
59
+ self.num_layers = num_layers
60
+ self.max_input_len = max_input_len
61
+ self.dropout = dropout
62
+ if not dim_feedforward:
63
+ dim_feedforward = hidden_size * 4
64
+ self.dim_feedforward = dim_feedforward
65
+
66
+ # Embedding layers
67
+ self.src_emb = nn.Embedding(input_dim, hidden_size)
68
+ self.pos_emb = PositionalEncoding(hidden_size, max_input_len, dropout)
69
+
70
+ # Encoder layers
71
+ self.layers = nn.ModuleList([EncoderLayer(hidden_size, n_heads,
72
+ self.d_k, self.d_v,
73
+ dropout, dim_feedforward)
74
+ for _ in range(num_layers)])
75
+
76
+ # Prediction head - similar to your implementation but with configurable number of tasks
77
+ self.regressor = nn.Sequential(
78
+ nn.Dropout(dropout),
79
+ nn.Linear(hidden_size, hidden_size),
80
+ nn.SiLU(),
81
+ nn.Linear(hidden_size, num_task)
82
+ )
83
+
84
+ def initialize_parameters(self, seed=None):
85
+ """Initialize model parameters randomly.
86
+
87
+ Parameters
88
+ ----------
89
+ seed : int, optional
90
+ Random seed for reproducibility.
91
+ """
92
+ if seed is not None:
93
+ torch.manual_seed(seed)
94
+
95
+ # Initialize embedding
96
+ init_weights(self.src_emb)
97
+
98
+ # Initialize layers
99
+ for layer in self.layers:
100
+ for p in layer.parameters():
101
+ if p.dim() > 1:
102
+ nn.init.xavier_uniform_(p)
103
+
104
+ # Initialize regressor
105
+ for module in self.regressor:
106
+ if hasattr(module, 'weight'):
107
+ init_weights(module)
108
+
109
+ def compute_loss(self, batched_input, batched_label, criterion):
110
+ """Compute the loss for a batch of data.
111
+
112
+ Parameters
113
+ ----------
114
+ batched_input : torch.Tensor
115
+ Batch of input sequences, shape (batch_size, seq_len).
116
+ batched_label : torch.Tensor
117
+ Batch of target values, shape (batch_size, num_task).
118
+ criterion : callable
119
+ Loss function to use.
120
+
121
+ Returns
122
+ -------
123
+ torch.Tensor
124
+ Scalar loss value.
125
+ """
126
+ # Get predictions
127
+ prediction = self(batched_input)["prediction"]
128
+
129
+ # Convert labels to float32
130
+ target = batched_label.to(torch.float32)
131
+
132
+ # Create mask for valid labels (handles NaN values)
133
+ is_labeled = batched_label == batched_label
134
+
135
+ # Compute loss only on valid labels
136
+ loss = criterion(prediction.to(torch.float32)[is_labeled], target[is_labeled])
137
+ return loss
138
+
139
+ def forward(self, batched_input):
140
+ """Forward pass of the model.
141
+
142
+ Parameters
143
+ ----------
144
+ batched_input : torch.Tensor
145
+ Batch of input sequences, shape (batch_size, seq_len).
146
+
147
+ Returns
148
+ -------
149
+ dict
150
+ Dictionary containing:
151
+ - prediction: Model predictions (shape: [batch_size, num_task])
152
+ """
153
+ # Create padding mask
154
+ pad_mask = get_attn_pad_mask(batched_input, batched_input)
155
+
156
+ # Embedding
157
+ enc_outputs = self.src_emb(batched_input)
158
+ enc_outputs = self.pos_emb(enc_outputs)
159
+
160
+ # Encoder layers
161
+ enc_self_attns = []
162
+ for layer in self.layers:
163
+ enc_outputs, enc_self_attn = layer(enc_outputs, pad_mask)
164
+ enc_self_attns.append(enc_self_attn)
165
+
166
+ # Global pooling for sequence representation
167
+ # Use CLS token (first token) as the sequence representation
168
+ cls_token = enc_outputs[:, 0, :]
169
+
170
+ # Prediction
171
+ prediction = self.regressor(cls_token)
172
+
173
+ return {"prediction": prediction}
174
+
175
+
176
+ class PositionalEncoding(nn.Module):
177
+ """Positional encoding layer for transformer.
178
+
179
+ Parameters
180
+ ----------
181
+ hidden_size : int
182
+ Dimension of hidden layers.
183
+ max_len : int
184
+ Maximum sequence length.
185
+ dropout : float
186
+ Dropout probability.
187
+ """
188
+ def __init__(self, hidden_size, max_len, dropout):
189
+ super(PositionalEncoding, self).__init__()
190
+ self.dropout = nn.Dropout(p=dropout)
191
+
192
+ # Create positional encoding table
193
+ pe = torch.zeros(max_len, hidden_size)
194
+ position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
195
+
196
+ # consider the situation that hidden_size is not even
197
+ div_terms_size = hidden_size // 2 if hidden_size % 2 == 0 else (hidden_size + 1) // 2
198
+ div_term = torch.exp(torch.arange(0, div_terms_size * 2, 2).float() * (-math.log(10000.0) / hidden_size))
199
+
200
+ # make sure the size of pe is (max_len, hidden_size)
201
+ if hidden_size % 2 == 1:
202
+ div_term = div_term[:div_terms_size]
203
+ pe[:, 0::2] = torch.sin(position * div_term[:hidden_size//2 + hidden_size%2])
204
+ pe[:, 1::2] = torch.cos(position * div_term[:hidden_size//2])
205
+ pe = pe.unsqueeze(0)
206
+
207
+ # Register buffer (not a parameter but should be saved)
208
+ self.register_buffer('pe', pe)
209
+
210
+ def forward(self, x):
211
+ """
212
+ Parameters
213
+ ----------
214
+ x : torch.Tensor
215
+ Input tensor of shape (batch_size, seq_len, hidden_size)
216
+
217
+ Returns
218
+ -------
219
+ torch.Tensor
220
+ Tensor with added positional encoding
221
+ """
222
+ x = x + self.pe[:, :x.size(1), :]
223
+ return self.dropout(x)
224
+
225
+
226
+ class EncoderLayer(nn.Module):
227
+ """Transformer encoder layer.
228
+
229
+ Parameters
230
+ ----------
231
+ hidden_size : int
232
+ Dimension of hidden layers.
233
+ n_heads : int
234
+ Number of attention heads.
235
+ d_k : int
236
+ Dimension of key/query.
237
+ d_v : int
238
+ Dimension of value.
239
+ dropout : float
240
+ Dropout probability.
241
+ """
242
+ def __init__(self, hidden_size, n_heads, d_k, d_v, dropout, dim_feedforward):
243
+ super(EncoderLayer, self).__init__()
244
+ self.self_attn = MultiHeadAttention(hidden_size, n_heads, d_k, d_v)
245
+ self.pos_ffn = PositionwiseFeedForward(hidden_size, dim_feedforward, dropout)
246
+ self.layer_norm1 = nn.LayerNorm(hidden_size)
247
+ self.layer_norm2 = nn.LayerNorm(hidden_size)
248
+ self.dropout = nn.Dropout(dropout)
249
+
250
+ def forward(self, enc_inputs, self_attn_mask):
251
+ """
252
+ Parameters
253
+ ----------
254
+ enc_inputs : torch.Tensor
255
+ Input tensor of shape (batch_size, seq_len, hidden_size)
256
+ self_attn_mask : torch.Tensor
257
+ Attention mask of shape (batch_size, seq_len, seq_len)
258
+
259
+ Returns
260
+ -------
261
+ torch.Tensor, torch.Tensor
262
+ Output tensor and attention weights
263
+ """
264
+ # Self-attention with residual connection and layer normalization
265
+ attn_outputs, attn = self.self_attn(enc_inputs, enc_inputs, enc_inputs, self_attn_mask)
266
+ attn_outputs = self.dropout(attn_outputs)
267
+ outputs = self.layer_norm1(enc_inputs + attn_outputs)
268
+
269
+ # Feed-forward with residual connection and layer normalization
270
+ ffn_outputs = self.pos_ffn(outputs)
271
+ ffn_outputs = self.dropout(ffn_outputs)
272
+ outputs = self.layer_norm2(outputs + ffn_outputs)
273
+
274
+ return outputs, attn
275
+
276
+
277
+ class MultiHeadAttention(nn.Module):
278
+ """Multi-head attention layer.
279
+
280
+ Parameters
281
+ ----------
282
+ hidden_size : int
283
+ Dimension of hidden layers.
284
+ n_heads : int
285
+ Number of attention heads.
286
+ d_k : int
287
+ Dimension of key/query.
288
+ d_v : int
289
+ Dimension of value.
290
+ """
291
+ def __init__(self, hidden_size, n_heads, d_k, d_v):
292
+ super(MultiHeadAttention, self).__init__()
293
+ self.hidden_size = hidden_size
294
+ self.n_heads = n_heads
295
+ self.d_k = d_k
296
+ self.d_v = d_v
297
+
298
+ self.W_Q = nn.Linear(hidden_size, d_k * n_heads, bias=False)
299
+ self.W_K = nn.Linear(hidden_size, d_k * n_heads, bias=False)
300
+ self.W_V = nn.Linear(hidden_size, d_v * n_heads, bias=False)
301
+ self.fc = nn.Linear(n_heads * d_v, hidden_size, bias=False)
302
+
303
+ def forward(self, Q, K, V, attn_mask):
304
+ """
305
+ Parameters
306
+ ----------
307
+ Q, K, V : torch.Tensor
308
+ Query, key and value tensors of shape (batch_size, seq_len, hidden_size)
309
+ attn_mask : torch.Tensor
310
+ Attention mask of shape (batch_size, seq_len, seq_len)
311
+
312
+ Returns
313
+ -------
314
+ torch.Tensor, torch.Tensor
315
+ Output tensor and attention weights
316
+ """
317
+ batch_size = Q.size(0)
318
+ residual = Q
319
+
320
+ # Transform to multi-head queries, keys, and values
321
+ Q = self.W_Q(Q).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
322
+ K = self.W_K(K).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
323
+ V = self.W_V(V).view(batch_size, -1, self.n_heads, self.d_v).transpose(1, 2)
324
+
325
+ # Expand mask for multi-head attention
326
+ if attn_mask is not None:
327
+ attn_mask = attn_mask.unsqueeze(1).repeat(1, self.n_heads, 1, 1)
328
+
329
+ # Calculate attention
330
+ context, attn = scaled_dot_product_attention(Q, K, V, attn_mask)
331
+
332
+ # Concatenate heads and put through final linear layer
333
+ context = context.transpose(1, 2).reshape(batch_size, -1, self.n_heads * self.d_v)
334
+ output = self.fc(context)
335
+
336
+ return output, attn
337
+
338
+
339
+ class PositionwiseFeedForward(nn.Module):
340
+ """Position-wise feed-forward network.
341
+
342
+ Parameters
343
+ ----------
344
+ d_in : int
345
+ Input dimension.
346
+ d_ff : int
347
+ Hidden dimension.
348
+ dropout : float
349
+ Dropout probability.
350
+ """
351
+ def __init__(self, d_in, d_ff, dropout):
352
+ super(PositionwiseFeedForward, self).__init__()
353
+ self.fc1 = nn.Linear(d_in, d_ff)
354
+ self.fc2 = nn.Linear(d_ff, d_in)
355
+ self.dropout = nn.Dropout(dropout)
356
+ self.activation = nn.ReLU()
357
+
358
+ def forward(self, x):
359
+ """
360
+ Parameters
361
+ ----------
362
+ x : torch.Tensor
363
+ Input tensor of shape (batch_size, seq_len, d_in)
364
+
365
+ Returns
366
+ -------
367
+ torch.Tensor
368
+ Output tensor of shape (batch_size, seq_len, d_in)
369
+ """
370
+ return self.fc2(self.dropout(self.activation(self.fc1(x))))
371
+
372
+
373
+ def scaled_dot_product_attention(Q, K, V, mask=None):
374
+ """Calculate scaled dot-product attention.
375
+
376
+ Parameters
377
+ ----------
378
+ Q, K, V : torch.Tensor
379
+ Query, key and value tensors
380
+ mask : torch.Tensor, optional
381
+ Attention mask
382
+
383
+ Returns
384
+ -------
385
+ torch.Tensor, torch.Tensor
386
+ Context tensor and attention weights
387
+ """
388
+ # Calculate attention scores
389
+ scores = torch.matmul(Q, K.transpose(-1, -2)) / math.sqrt(Q.size(-1))
390
+
391
+ # Apply mask if provided
392
+ if mask is not None:
393
+ scores = scores.masked_fill(mask, -1e9)
394
+
395
+ # Apply softmax to get attention weights
396
+ attn = F.softmax(scores, dim=-1)
397
+
398
+ # Calculate context by weighted sum of values
399
+ context = torch.matmul(attn, V)
400
+
401
+ return context, attn
402
+
403
+
404
+ def get_attn_pad_mask(seq_q, seq_k):
405
+ """Create padding mask for attention.
406
+
407
+ Parameters
408
+ ----------
409
+ seq_q, seq_k : torch.Tensor
410
+ Query and key sequences
411
+
412
+ Returns
413
+ -------
414
+ torch.Tensor
415
+ Boolean mask where True indicates padding positions
416
+ """
417
+ batch_size, len_q = seq_q.size()
418
+ batch_size, len_k = seq_k.size()
419
+
420
+ # Create mask for padding
421
+ pad_attn_mask = seq_k.data.eq(0).unsqueeze(1)
422
+
423
+ # Expand mask to match attention matrix dimensions
424
+ return pad_attn_mask.expand(batch_size, len_q, len_k)