Views
No views yet
1python -m venv .venv
2source .venv/bin/activate # On Windows: .venv\Scripts\activate
3pip install -r requirements.txtNote: SMIRK tokenizers require Rust to be installed. See the Rust installation guide for details.
uv run ....1 """
2 Minimal example for getting embeddings from a pretrained model and calculating similarity.
3
4 Usage:
5 uv run embeddings_similarity.py
6
7 """
8 # /// script
9 # requires-python = ">=3.9"
10 # dependencies = [
11 # "torch>=2.0.0",
12 # "transformers>=4.30.0",
13 # "smirk>=0.1.0",
14 # "numpy>=1.20.0",
15 # "rdkit>=2022.0.0",
16 # ]
17 # ///
18
19
20 import torch
21 import numpy as np
22 from rdkit import Chem
23 from smirk import SmirkTokenizerFast
24 from transformers import AutoModel, AutoTokenizer
25
26
27 def kekulize_smiles(smiles):
28 """Convert SMILES to kekulized form."""
29 mol = Chem.MolFromSmiles(smiles)
30 if mol is None:
31 raise ValueError(f"Invalid SMILES: {smiles}")
32 Chem.Kekulize(mol)
33 return Chem.MolToSmiles(mol, kekuleSmiles=True)
34
35
36 def get_embeddings(smiles_list, model, tokenizer, device="cpu"):
37 """Get embeddings for a list of SMILES strings."""
38 # MIST was pretrained on Kekulize SMILES
39 kekulized_smiles = [kekulize_smiles(s) for s in smiles_list]
40
41 # Tokenize
42 inputs = tokenizer(
43 kekulized_smiles,
44 padding=True,
45 truncation=True,
46 max_length=512,
47 return_tensors="pt"
48 )
49
50 # Move to device
51 inputs = {k: v.to(device) for k, v in inputs.items()}
52
53 # Get embeddings
54 model.eval()
55 with torch.no_grad():
56 outputs = model(**inputs)
57 embeddings = outputs.last_hidden_state[:, 0, :].cpu().numpy()
58
59 return embeddings
60
61
62 def cosine_similarity(emb1, emb2):
63 """Calculate cosine similarity between two embeddings."""
64 dot_product = np.dot(emb1, emb2)
65 norm1 = np.linalg.norm(emb1)
66 norm2 = np.linalg.norm(emb2)
67 return dot_product / (norm1 * norm2)
68
69
70 def main():
71 # Load pretrained model
72 model_path = "mist-models/mist-28M-ti624ev1"
73 print(f"Loading model from {model_path}...")
74 tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
75 model = AutoModel.from_pretrained(model_path, trust_remote_code=True)
76
77 device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
78 model = model.to(device)
79 print(f"Using device: {device}")
80
81 # Example SMILES
82 smiles_list = [
83 "CCO", # Ethanol
84 "CC(C)O", # Isopropanol
85 "CCCO", # Propanol
86 "c1ccccc1", # Benzene
87 "c1ccccc1O", # Phenol
88 "CC(=O)O", # Acetic acid
89 ]
90
91 print(f"\nGetting embeddings for {len(smiles_list)} molecules...")
92 embeddings = get_embeddings(smiles_list, model, tokenizer, device)
93 print(f"Embedding shape: {embeddings.shape}")
94
95
96 # Query similarity example
97 query = "CCCCO" # Butanol
98 print(f"Query: {query}")
99
100 query_emb = get_embeddings([query], model, tokenizer, device)[0]
101
102 print("\nMost similar molecules:")
103 query_sims = []
104 for i, smiles in enumerate(smiles_list):
105 sim = cosine_similarity(query_emb, embeddings[i])
106 query_sims.append((smiles, sim))
107
108 query_sims.sort(key=lambda x: x[1], reverse=True)
109 for i, (smiles, sim) in enumerate(query_sims[:3], 1):
110 print(f" {i}. {smiles:15s} : {sim:.4f}")
111
112 if __name__ == "__main__":
113 main()1 """
2 Minimal example for finetuning a pretrained model on a CSV using HuggingFace Trainer.
3
4 CSV format:
5 smiles,target
6 CCO,1.23
7 CC(C)O,2.45
8
9 Usage:
10 uv run finetune_minimal.py
11 """
12
13 # /// script
14 # requires-python = ">=3.9"
15 # dependencies = [
16 # "torch>=2.0.0",
17 # "transformers>=4.30.0",
18 # "datasets>=2.0.0",
19 # "smirk>=0.1.0",
20 # "accelerate>=0.26.0",
21 # "rdkit>=2022.0.0",
22 # ]
23 # ///
24
25 import torch
26 import torch.nn as nn
27 from rdkit import Chem
28 from smirk import SmirkTokenizerFast
29 from datasets import load_dataset
30 from transformers import (
31 AutoModel,
32 AutoTokenizer,
33 Trainer,
34 TrainingArguments,
35 DataCollatorWithPadding,
36 )
37 from pathlib import Path
38
39
40 def kekulize_smiles(smiles):
41 """Convert SMILES to kekulized form."""
42 mol = Chem.MolFromSmiles(smiles)
43 if mol is None:
44 raise ValueError(f"Invalid SMILES: {smiles}")
45 Chem.Kekulize(mol)
46 return Chem.MolToSmiles(mol, kekuleSmiles=True)
47
48
49 class RegressionModel(nn.Module):
50 """Model with encoder + regression task head."""
51
52 def __init__(self, encoder, hidden_size=768, dropout=0.1):
53 super().__init__()
54 self.encoder = encoder
55 self.task_head = nn.Sequential(
56 nn.Linear(hidden_size, hidden_size),
57 nn.ReLU(),
58 nn.Dropout(dropout),
59 nn.Linear(hidden_size, 1),
60 )
61
62 def forward(self, input_ids, attention_mask, labels=None):
63 # Get encoder outputs
64 encoder_output = self.encoder(
65 input_ids=input_ids, attention_mask=attention_mask
66 )
67 # Use first token
68 pooled = encoder_output.last_hidden_state[:, 0, :]
69
70 # Regression prediction
71 logits = self.task_head(pooled)
72
73 loss = None
74 if labels is not None:
75 loss_fn = nn.MSELoss()
76 loss = loss_fn(logits.squeeze(-1), labels)
77
78 return {"loss": loss, "logits": logits} if loss is not None else {"logits": logits}
79
80
81 def tokenize_function(examples, tokenizer):
82 """Tokenize SMILES strings (kekulized)."""
83 # MIST was pretrained on kekulized SMILES
84 kekulized = [kekulize_smiles(s) for s in examples["smiles"]]
85 return tokenizer(
86 kekulized,
87 padding="max_length",
88 truncation=True,
89 max_length=512,
90 )
91
92
93 def main():
94 # 1. Load dataset from CSV
95 dataset = load_dataset(
96 "csv",
97 data_files={"train": "https://deepchemdata.s3-us-west-1.amazonaws.com/datasets/qm9.csv"},
98 )["train"]
99
100 # Split into train/test
101 dataset = dataset.train_test_split(test_size=0.2, seed=42)
102
103 # 2. Load pretrained encoder and tokenizer
104 model_path = "mist-models/mist-28M-ti624ev1"
105 tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
106 encoder = AutoModel.from_pretrained(model_path, trust_remote_code=True)
107
108 # 3. Create regression model with task head
109 model = RegressionModel(
110 encoder=encoder,
111 hidden_size=encoder.config.hidden_size,
112 dropout=0.1,
113 )
114
115 # 4. Tokenize dataset
116 dataset = dataset.map(
117 lambda x: tokenize_function(x, tokenizer),
118 batched=True,
119 desc="Tokenizing",
120 )
121
122 # Rename target column to labels (Trainer expects this)
123 dataset = dataset.rename_column("lumo", "labels")
124
125 # Set format for PyTorch
126 dataset.set_format(
127 type="torch",
128 columns=["input_ids", "attention_mask", "labels"],
129 )
130
131 # 5. Setup training arguments
132 training_args = TrainingArguments(
133 output_dir="./finetuned_model",
134 num_train_epochs=10,
135 per_device_train_batch_size=32,
136 per_device_eval_batch_size=32,
137 learning_rate=1e-5,
138 warmup_ratio=0.1,
139 logging_steps=10,
140 eval_strategy="epoch",
141 save_strategy="epoch",
142 load_best_model_at_end=True,
143 metric_for_best_model="eval_loss",
144 save_total_limit=2,
145 report_to="none", # Disable wandb/tensorboard
146 )
147
148 # 6. Create data collator
149 data_collator = DataCollatorWithPadding(tokenizer)
150
151 # 7. Create Trainer
152 trainer = Trainer(
153 model=model,
154 args=training_args,
155 train_dataset=dataset["train"],
156 eval_dataset=dataset["test"],
157 processing_class=tokenizer,
158 data_collator=data_collator,
159 )
160
161 # 8. Train!
162 print("Starting training...")
163 trainer.train()
164
165 # 9. Save final model
166 print("Saving model...")
167 trainer.save_model("./finetuned_model")
168 tokenizer.save_pretrained("./finetuned_model")
169 print("Done!")
170
171 # 10. Inference example with the finetuned model
172 test_smiles = [
173 "CCO",
174 "CC(C)O",
175 "CCCC",
176 "c1ccccc1",
177 "CC(=O)O"
178 ]
179
180 # Kekulize and tokenize
181 kekulized_test = [kekulize_smiles(s) for s in test_smiles]
182 inputs = tokenizer(
183 kekulized_test,
184 padding=True,
185 truncation=True,
186 max_length=512,
187 return_tensors="pt"
188 )
189
190 # Move to device and run inference
191 device = next(model.parameters()).device
192 inputs = {k: v.to(device) for k, v in inputs.items()}
193
194 model.eval()
195 with torch.no_grad():
196 outputs = model(**inputs)
197 predictions = outputs["logits"].squeeze(-1).cpu()
198
199 print("\nPredictions:")
200 for smiles, pred in zip(test_smiles, predictions):
201 print(f" {smiles} → {pred.item():.4f} Hartree")
202
203
204 if __name__ == "__main__":
205 main()RoBERTa-PreLayerNorm encoder with 8 layers, a hidden size of 512, intermediate size of 2048, 8 attention heads and maximum sequence length of 2048.deepspeed.ops.lamb.FusedLAMB1@online{MIST,
2 title = {Foundation Models for Discovery and Exploration in Chemical Space},
3 author = {Wadell, Alexius and Bhutani, Anoushka and Azumah, Victor and Ellis-Mohr, Austin R. and Kelly, Celia and Zhao, Hancheng and Nayak, Anuj K. and Hegazy, Kareem and Brace, Alexander and Lin, Hongyi and Emani, Murali and Vishwanath, Venkatram and Gering, Kevin and Alkan, Melisa and Gibbs, Tom and Wells, Jack and Varshney, Lav R. and Ramsundar, Bharath and Duraisamy, Karthik and Mahoney, Michael W. and Ramanathan, Arvind and Viswanathan, Venkatasubramanian},
4 date = {2025-10-20},
5 eprint = {2510.18900},
6 eprinttype = {arXiv},
7 eprintclass = {physics},
8 doi = {10.48550/arXiv.2510.18900},
9 url = {http://arxiv.org/abs/2510.18900},
10}