ai4sci-vs-contrastive-scoring
Description
Task: Scoring Objective Design for Virtual Screening
Research Question
Design the scoring objective — including projection heads, embedding space, and training loss — for contrastive protein-ligand virtual screening. Given pretrained backbone encoders (UniMol for molecules/pockets, ESM2 for protein sequences) that are fine-tuned jointly end-to-end with the scoring module, how should their features be projected, embedded, and trained to best discriminate active binders from decoys?
Background
Virtual screening computationally ranks large compound libraries against a protein target to identify potential drug candidates. Modern approaches use learned representations: encode protein pockets and molecules into a shared embedding space, then rank by similarity. Key design choices include:
- Projection heads: How to project backbone features (512-dim UniMol, 480-dim ESM2) into a shared space
- Embedding geometry: Euclidean (L2-normalized dot product), hyperbolic (Lorentz hyperboloid), spherical, or other manifolds
- Training loss: In-batch contrastive (CLIP-style), ranking-aware losses, activity-dependent constraints, cone hierarchy
Existing approaches range from simple CLIP-style contrastive learning (DrugCLIP) to hyperbolic geometry with cone hierarchy constraints (HypSeek).
What to Implement
Implement the CustomScoring class in custom_scoring.py. You must implement:
__init__: Define projection heads, embedding parameters, loss hyperparametersproject_mol(mol_feat): Project molecule features [B, 512] → [B, embed_dim]project_pocket(poc_feat): Project pocket features [B, 512] → [B, embed_dim]project_protein(prot_feat): Project protein features [B, 480] → [B, embed_dim]compute_loss(mol_emb, poc_emb, prot_emb, batch_list, act_list, ...): Training lossscore(mol_reps, pocket_reps, prot_reps): Evaluation scoring (numpy arrays)
Available Components
- Backbone features (fine-tuned jointly):
mol_feat[B, 512],poc_feat[B, 512],prot_feat[B, 480] - Lorentz hyperbolic operations:
exp_map0,pairwise_dist,half_aperture,oxy_anglefromunimol.losses.lorentz - Training data provides:
batch_list(pocket→ligand mapping),act_list(pIC50 activities),uniprot_poc/mol(for false-negative masking),pocket_lig_smiles/lig_smiles(for duplicate masking)
Evaluation
The model is evaluated on three virtual screening benchmarks (zero-shot, no target-specific training):
- DUD-E (102 targets): Active compounds vs property-matched decoys
- LIT-PCBA (15 targets): Realistic screening with confirmed actives/inactives
- DEKOIS 2.0 (81 targets): Challenging decoy benchmark
Metrics (averaged across targets): AUROC, BEDROC (α=80.5), EF at 0.5%/1%/5%
Editable Region
The entire custom_scoring.py file is editable. You may define any helper classes or functions within this file. The backbone encoders and training loop are fixed; backbone parameters are loaded from pretrained weights and fine-tuned jointly with the scoring module.
Code
1"""Custom scoring module for contrastive virtual screening.23This module defines the projection heads, embedding space mapping, and4training loss for protein-ligand virtual screening.56Interface:7project_mol(mol_feat) -> [B, embed_dim] molecule embeddings8project_pocket(poc_feat) -> [B, embed_dim] pocket embeddings9project_protein(prot_feat) -> [B, embed_dim] protein embeddings10compute_loss(mol_emb, poc_emb, prot_emb, ...) -> (loss, dict)11score(mol_reps, pocket_reps, prot_reps) -> [N_mol] numpy scores1213Available utilities (imported in model wrapper):14torch, torch.nn, torch.nn.functional, numpy, math15lorentz ops: from unimol.losses.lorentz import exp_map0, pairwise_dist,
1"""Custom virtual screening model wrapper (FIXED — do not modify).23This model wraps UniMol + ESM2 backbone encoders and delegates4projection + embedding logic to the editable CustomScoring module.5Backbones are FINE-TUNED jointly with the scoring head to match the6HypSeek training protocol (paper uses joint end-to-end training).7"""89import argparse10import math11import numpy as np12import torch13import torch.nn as nn14import torch.nn.functional as F15from unicore import utils
1"""Custom virtual screening loss wrapper (FIXED — do not modify).23Delegates all loss computation to model.scoring.compute_loss().4"""56import math7import numpy as np8import torch9import torch.nn.functional as F10from unicore.losses import UnicoreLoss, register_loss11from unicore import metrics12from sklearn.metrics import roc_auc_score13from rdkit.ML.Scoring.Scoring import CalcBEDROC1415
Additional context files (read-only):
HypSeek/unimol/losses/lorentz.py
Results
| Model | Type | auc mean dude ↑ | bedroc mean dude ↑ | ef005 mean dude ↑ | ef01 mean dude ↑ | ef05 mean dude ↑ | ef0005 mean dude ↑ | ef001 mean dude ↑ | ef002 mean dude ↑ | auc mean dekois ↑ | bedroc mean dekois ↑ | ef005 mean dekois ↑ | ef01 mean dekois ↑ | ef05 mean dekois ↑ | ef0005 mean dekois ↑ | ef001 mean dekois ↑ | ef002 mean dekois ↑ | auc mean lit-pcba ↑ | bedroc mean lit-pcba ↑ | ef005 mean lit-pcba ↑ | ef01 mean lit-pcba ↑ | ef05 mean lit-pcba ↑ | ef0005 mean lit-pcba ↑ | ef001 mean lit-pcba ↑ | ef002 mean lit-pcba ↑ |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hcc | baseline | 0.732 | 0.203 | 13.076 | 11.452 | 5.920 | 13.076 | 11.452 | 8.917 | 0.694 | 0.219 | 7.978 | 7.182 | 4.709 | 7.978 | 7.182 | 6.305 | 0.565 | 0.033 | 1.977 | 2.862 | 1.670 | 1.977 | 2.862 | 2.343 |
| hcc_hyp_cone | baseline | 0.702 | 0.187 | 12.100 | 10.371 | 5.519 | 12.100 | 10.371 | 8.226 | 0.681 | 0.217 | 8.148 | 7.507 | 4.487 | 8.148 | 7.507 | 6.260 | 0.546 | 0.032 | 3.368 | 2.596 | 1.799 | 3.367 | 2.596 | 2.190 |
| vanilla_clip | baseline | 0.683 | 0.167 | 10.447 | 9.441 | 5.145 | 10.447 | 9.441 | 7.613 | 0.615 | 0.141 | 4.591 | 4.444 | 3.505 | 4.591 | 4.443 | 4.024 | 0.525 | 0.033 | 3.489 | 2.652 | 1.538 | 3.489 | 2.652 | 1.776 |