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.
- torch_molecule/__init__.py +70 -0
- torch_molecule/base/__init__.py +8 -0
- torch_molecule/base/base.py +396 -0
- torch_molecule/base/encoder.py +44 -0
- torch_molecule/base/generator.py +24 -0
- torch_molecule/base/old_encoder.py +322 -0
- torch_molecule/base/old_predictor.py +904 -0
- torch_molecule/base/old_predictor2.py +436 -0
- torch_molecule/base/predictor.py +112 -0
- torch_molecule/encoder/attrmask/__init__.py +3 -0
- torch_molecule/encoder/attrmask/model.py +113 -0
- torch_molecule/encoder/attrmask/modeling_attrmask.py +304 -0
- torch_molecule/encoder/constant.py +13 -0
- torch_molecule/encoder/contextpred/__init__.py +3 -0
- torch_molecule/encoder/contextpred/model.py +207 -0
- torch_molecule/encoder/contextpred/modeling_contextpred.py +299 -0
- torch_molecule/encoder/contextpred/utils.py +206 -0
- torch_molecule/encoder/edgepred/__init__.py +3 -0
- torch_molecule/encoder/edgepred/model.py +104 -0
- torch_molecule/encoder/edgepred/modeling_edgepred.py +285 -0
- torch_molecule/encoder/graphmae/__init__.py +3 -0
- torch_molecule/encoder/graphmae/dataloader.py +206 -0
- torch_molecule/encoder/graphmae/model.py +137 -0
- torch_molecule/encoder/graphmae/modeling_graphmae.py +323 -0
- torch_molecule/encoder/infograph/__init__.py +3 -0
- torch_molecule/encoder/infograph/loss_function.py +128 -0
- torch_molecule/encoder/infograph/model.py +92 -0
- torch_molecule/encoder/infograph/modeling_infograph.py +301 -0
- torch_molecule/encoder/infograph/module.py +31 -0
- torch_molecule/encoder/moama/__init__.py +3 -0
- torch_molecule/encoder/moama/model.py +131 -0
- torch_molecule/encoder/moama/modeling_moama.py +307 -0
- torch_molecule/encoder/moama/utils.py +143 -0
- torch_molecule/encoder/pretrained/__init__.py +3 -0
- torch_molecule/encoder/pretrained/modeling_pretrained.py +274 -0
- torch_molecule/encoder/supervised/__init__.py +3 -0
- torch_molecule/encoder/supervised/model.py +97 -0
- torch_molecule/encoder/supervised/modeling_supervised.py +354 -0
- torch_molecule/generator/digress/__init__.py +3 -0
- torch_molecule/generator/digress/diffusion.py +194 -0
- torch_molecule/generator/digress/modeling_digress.py +503 -0
- torch_molecule/generator/digress/transformer.py +360 -0
- torch_molecule/generator/digress/utils.py +196 -0
- torch_molecule/generator/gdss/__init__.py +3 -0
- torch_molecule/generator/gdss/layers.py +257 -0
- torch_molecule/generator/gdss/model.py +204 -0
- torch_molecule/generator/gdss/modeling_gdss.py +474 -0
- torch_molecule/generator/gdss/sde.py +334 -0
- torch_molecule/generator/gdss/solver.py +227 -0
- torch_molecule/generator/gdss/toremove/ScoreNetworkA.py +167 -0
- torch_molecule/generator/gdss/toremove/ScoreNetworkX.py +85 -0
- torch_molecule/generator/gdss/utils.py +240 -0
- torch_molecule/generator/graph_dit/__init__.py +3 -0
- torch_molecule/generator/graph_dit/diffusion.py +188 -0
- torch_molecule/generator/graph_dit/modeling_graph_dit.py +581 -0
- torch_molecule/generator/graph_dit/transformer.py +207 -0
- torch_molecule/generator/graph_dit/utils.py +220 -0
- torch_molecule/generator/graph_ga/__init__.py +3 -0
- torch_molecule/generator/graph_ga/crossover.py +182 -0
- torch_molecule/generator/graph_ga/modeling_graph_ga.py +310 -0
- torch_molecule/generator/graph_ga/mutate.py +136 -0
- torch_molecule/generator/graph_ga/oracle.py +95 -0
- torch_molecule/generator/jtvae/__init__.py +3 -0
- torch_molecule/generator/jtvae/jtnn/chemutils.py +450 -0
- torch_molecule/generator/jtvae/jtnn/datautils.py +127 -0
- torch_molecule/generator/jtvae/jtnn/jtmpn.py +140 -0
- torch_molecule/generator/jtvae/jtnn/jtnn_dec.py +350 -0
- torch_molecule/generator/jtvae/jtnn/jtnn_enc.py +131 -0
- torch_molecule/generator/jtvae/jtnn/mol_tree.py +111 -0
- torch_molecule/generator/jtvae/jtnn/mpn.py +129 -0
- torch_molecule/generator/jtvae/jtnn/nnutils.py +66 -0
- torch_molecule/generator/jtvae/jtnn/vocab.py +31 -0
- torch_molecule/generator/jtvae/jtnn_vae.py +246 -0
- torch_molecule/generator/jtvae/modeling_jtvae.py +247 -0
- torch_molecule/generator/lstm/__init__.py +3 -0
- torch_molecule/generator/lstm/action_sampler.py +91 -0
- torch_molecule/generator/lstm/lstm.py +59 -0
- torch_molecule/generator/lstm/modeling_lstm.py +263 -0
- torch_molecule/generator/lstm/smiles_char_dict.py +123 -0
- torch_molecule/generator/lstm/utils.py +62 -0
- torch_molecule/generator/molgpt/__init__.py +3 -0
- torch_molecule/generator/molgpt/dataset.py +87 -0
- torch_molecule/generator/molgpt/gpt.py +251 -0
- torch_molecule/generator/molgpt/modeling_molgpt.py +427 -0
- torch_molecule/generator/pretrained/modeling_pretrained.py +1 -0
- torch_molecule/nn/__init__.py +16 -0
- torch_molecule/nn/attention.py +63 -0
- torch_molecule/nn/embedder.py +183 -0
- torch_molecule/nn/gnn.py +631 -0
- torch_molecule/nn/mlp.py +34 -0
- torch_molecule/predictor/bfgnn/__init__.py +3 -0
- torch_molecule/predictor/bfgnn/model.py +116 -0
- torch_molecule/predictor/bfgnn/modeling_bfgnn.py +81 -0
- torch_molecule/predictor/dir/__init__.py +3 -0
- torch_molecule/predictor/dir/model.py +235 -0
- torch_molecule/predictor/dir/modeling_dir.py +161 -0
- torch_molecule/predictor/dir/utils.py +40 -0
- torch_molecule/predictor/gnn/__init__.py +3 -0
- torch_molecule/predictor/gnn/model.py +114 -0
- torch_molecule/predictor/gnn/modeling_gnn.py +706 -0
- torch_molecule/predictor/gnn/old_modeling_gnn.py +707 -0
- torch_molecule/predictor/grea/__init__.py +3 -0
- torch_molecule/predictor/grea/model.py +190 -0
- torch_molecule/predictor/grea/modeling_grea.py +107 -0
- torch_molecule/predictor/grea/old1_features.py +229 -0
- torch_molecule/predictor/grea/old1_modeling_grea.py +493 -0
- torch_molecule/predictor/grea/utils.py +97 -0
- torch_molecule/predictor/grin/__init__.py +3 -0
- torch_molecule/predictor/grin/model.py +116 -0
- torch_molecule/predictor/grin/modeling_grin.py +91 -0
- torch_molecule/predictor/irm/__init__.py +3 -0
- torch_molecule/predictor/irm/model.py +127 -0
- torch_molecule/predictor/irm/modeling_irm.py +179 -0
- torch_molecule/predictor/lstm/__init__.py +3 -0
- torch_molecule/predictor/lstm/model.py +145 -0
- torch_molecule/predictor/lstm/modeling_lstm.py +627 -0
- torch_molecule/predictor/lstm/token_from_smiles.py +85 -0
- torch_molecule/predictor/old_modeling_graph.py +650 -0
- torch_molecule/predictor/rpgnn/__init__.py +3 -0
- torch_molecule/predictor/rpgnn/model.py +185 -0
- torch_molecule/predictor/rpgnn/modeling_rpgnn.py +76 -0
- torch_molecule/predictor/sgir/__init__.py +3 -0
- torch_molecule/predictor/sgir/modeling_sgir.py +260 -0
- torch_molecule/predictor/sgir/strategy.py +266 -0
- torch_molecule/predictor/smiles_transformer/__init__.py +3 -0
- torch_molecule/predictor/smiles_transformer/model.py +424 -0
- torch_molecule/predictor/smiles_transformer/modeling_transformer.py +252 -0
- torch_molecule/predictor/ssr/__init__.py +3 -0
- torch_molecule/predictor/ssr/model.py +283 -0
- torch_molecule/predictor/ssr/modeling_ssr.py +267 -0
- torch_molecule/predictor/ssr/utils.py +185 -0
- torch_molecule/utils/__init__.py +31 -0
- torch_molecule/utils/checker.py +158 -0
- torch_molecule/utils/checkpoint.py +294 -0
- torch_molecule/utils/format.py +140 -0
- torch_molecule/utils/generic/metrics.py +530 -0
- torch_molecule/utils/generic/pseudo_tasks.py +11 -0
- torch_molecule/utils/generic/weights.py +30 -0
- torch_molecule/utils/graph/features.py +178 -0
- torch_molecule/utils/graph/graph_from_smiles.py +134 -0
- torch_molecule/utils/graph/graph_to_smiles.py +312 -0
- torch_molecule/utils/hf.py +232 -0
- torch_molecule/utils/search.py +64 -0
- torch_molecule-0.1.0.dev2.dist-info/METADATA +243 -0
- torch_molecule-0.1.0.dev2.dist-info/RECORD +148 -0
- torch_molecule-0.1.0.dev2.dist-info/WHEEL +5 -0
- torch_molecule-0.1.0.dev2.dist-info/licenses/LICENSE +21 -0
- 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,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)
|