File size: 5,714 Bytes
111d270 848dcb6 0b12001 111d270 a7ff6bf 111d270 e2dbbbe 7c72a8a e2dbbbe 0b12001 c4c625d 0b12001 d93c6e8 0b12001 d93c6e8 0b12001 38688e0 c4c625d eeff8a1 c4c625d eeff8a1 c4c625d e2dbbbe 8cfad4c 6d01d8f e2dbbbe c4c625d 111d270 0b12001 38688e0 62fd606 111d270 0b12001 111d270 42a2c29 111d270 42a2c29 cf604be 4792a76 cf604be e8dc124 cf604be 95582aa cf604be 4792a76 cf604be 4792a76 95582aa cf604be 95582aa cf604be 3be1d6d cf604be | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | from transformers import Pipeline
import torch
import torch.nn.functional as F
import numpy as np
import torch
import torch.nn.functional as F
import re
class SmilesDiffusionPipe(Pipeline):
def __init__(self, model, voc, **kwargs):
super().__init__(model=model, **kwargs)
self.voc = voc
def restore_halogen(self, smiles):
smiles = re.sub(r'(?<!\[)L(?!\])', 'Cl', smiles)
smiles = re.sub(r'(?<!\[)R(?!\])', 'Br', smiles)
return smiles
def replace_halogen(self, smiles):
return smiles.replace('Cl', 'L').replace('Br', 'R')
def tokenize_smiles(self, smiles):
regex = r'(\[[^\[\]]{1,6}\])'
smiles = self.replace_halogen(smiles)
char_list = re.split(regex, smiles)
tokenized = ['[GO]']
for char in char_list:
if char.startswith('['):
tokenized.append(char)
else:
chars = [unit for unit in char]
[tokenized.append(unit) for unit in chars]
tokenized.append("[EOS]")
return tokenized
def encode(self, char_list):
smiles_matrix = np.zeros(len(char_list), dtype=np.float32)
unk_id = self.voc.vocab.get("[UNK]")
if unk_id is None:
raise ValueError("[UNK] token not found in vocabulary.")
for i, char in enumerate(char_list):
smiles_matrix[i] = self.voc.vocab.get(char, unk_id)
return smiles_matrix
def decode(self, matrix):
""""Takes an array of indices (can be list of tensors or ints) and returns the corresponding SMILES"""
matrix = [int(i) for i in matrix] # This handles tensors from PyTorch
# Using tokenizer's decode method to convert token IDs to text
smiles = self.voc.decode(matrix)
smiles = self.restore_halogen(smiles)
return smiles
def _sanitize_parameters(self, **kwargs):
preprocess_kwargs = {}
postprocess_kwargs = {}
if "steps" in kwargs:
preprocess_kwargs["steps"] = kwargs["steps"]
if "k" in kwargs:
preprocess_kwargs["k"] = kwargs["k"]
return preprocess_kwargs, {}, postprocess_kwargs
def preprocess(self, smiles_str, steps=30, k=1):
tokenized = self.tokenize_smiles(smiles_str)
encoded = self.encode(tokenized)
return (encoded, steps, k)
def _forward(self, inputs):
encoded, steps, k = inputs
return self.unmask_partial_smiles(encoded, self.model, self.voc, steps, k)
def postprocess(self, result):
return result
def multinomial_sample(self, probs):
return torch.multinomial(probs, 1)
def unmask_partial_smiles(self, input_ids, model, voc, steps=30, k=3):
model.eval()
with torch.no_grad():
sequences = torch.tensor(input_ids, dtype=torch.long, device=self.device).unsqueeze(0)
mask_token = voc.vocab['[MASK]']
pad_token = voc.vocab.get('[PAD]', None)
total_tokens = sequences.size(1)
original_num_masked = (sequences == mask_token).sum().item()
print(f"Original number of masked tokens: {original_num_masked}")
if total_tokens == 0:
raise ValueError("Encoded SMILES has no tokens.")
if original_num_masked == 0:
print("No masked tokens found. Returning original SMILES.")
input_ids = input_ids[1:-1]
return self.decode(input_ids)
filled_tokens_confidence = {}
# --- ITERATION LOGIC: unmask one token at a time based on highest confidence ---
num_masks = (sequences == mask_token).sum().item()
for _ in range(num_masks):
# Estimate t per sequence
frac_masked = (sequences == mask_token).sum().item() / total_tokens
t = torch.tensor([frac_masked], device=self.device)
logits = model(sequences, t=t)
probs = F.softmax(logits, dim=-1)
mask_positions = (sequences == mask_token)
mask_indices = mask_positions.nonzero(as_tuple=False)
if len(mask_indices) == 0:
print("All tokens filled.")
break
masked_probs = probs[mask_positions] # (#masked, vocab)
sampled_ids = torch.multinomial(masked_probs, num_samples=1).squeeze(-1)
masked_confidence = torch.max(masked_probs, dim=-1).values
best_idx = torch.argmax(masked_confidence).item()
chosen_pos = mask_indices[best_idx] # [batch=0, position]
chosen_token = sampled_ids[best_idx].item()
chosen_conf = masked_confidence[best_idx].item()
sequences[0, chosen_pos[1]] = chosen_token
filled_tokens_confidence[chosen_pos[1].item()] = chosen_conf
print(f"Filled index {chosen_pos[1].item()} with token {chosen_token} (conf={chosen_conf:.4f})")
# Print confidence for all filled tokens
for idx, confidence in sorted(filled_tokens_confidence.items(), key=lambda x: x[0]):
print(f"Final confidence for token at index {idx}: {confidence:.3f}")
# Decode, removing <s> and </s>
decoded_tokens = [t for t in sequences[0].tolist() if t not in (voc.vocab.get('<s>'), voc.vocab.get('</s>'))]
if len(decoded_tokens) > 2:
decoded_tokens = decoded_tokens[1:-1]
decoded = self.decode(decoded_tokens).replace(" ", "")
return decoded |