| |
| |
| |
| import json |
|
|
| |
| import logging |
| import os |
| import random |
| from datetime import datetime |
| |
| |
| from xmlrpc.client import Boolean |
| import numpy as np |
| import torch |
| import pickle |
| from typing import Dict, List, Set, Tuple, Union |
| from tqdm import tqdm |
| from unicore import checkpoint_utils |
| import unicore |
| from unicore.data import (AppendTokenDataset, Dictionary, EpochShuffleDataset, |
| FromNumpyDataset, NestedDictionaryDataset, |
| PrependTokenDataset, RawArrayDataset,LMDBDataset, RawLabelDataset, |
| RightPadDataset, RightPadDataset2D, TokenizeDataset,SortDataset,data_utils) |
| from unicore.tasks import UnicoreTask, register_task |
| from unimol.data import (PairDataset, AffinityDataset, CroppingPocketDataset,CroppingDataset, |
| CrossDistanceDataset, DistanceDataset, |
| EdgeTypeDataset, KeyDataset, LengthDataset, |
| NormalizeDataset, NormalizeDockingPoseDataset, |
| PrependAndAppend2DDataset, RemoveHydrogenDataset, |
| RemoveHydrogenPocketDataset, RightPadDatasetCoord, |
| RightPadDatasetCross2D, TTADockingPoseDataset, AffinityTestDataset, AffinityValidDataset, AffinityMolDataset, AffinityPocketDataset, ResamplingDataset) |
| |
| from rdkit import Chem |
| from rdkit.Chem.Scaffolds import MurckoScaffold |
| from rdkit.ML.Scoring.Scoring import CalcBEDROC, CalcAUC, CalcEnrichment |
| from sklearn.metrics import roc_curve |
| logger = logging.getLogger(__name__) |
| import os |
| PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
|
|
|
|
| def make_mol(s: str, keep_h: bool, add_h: bool, keep_atom_map: bool): |
| """ |
| Builds an RDKit molecule from a SMILES string. |
| |
| :param s: SMILES string. |
| :param keep_h: Boolean whether to keep hydrogens in the input smiles. This does not add hydrogens, it only keeps them if they are specified. |
| :param add_h: Boolean whether to add hydrogens to the input smiles. |
| :param keep_atom_map: Boolean whether to keep the original atom mapping. |
| :return: RDKit molecule. |
| """ |
| params = Chem.SmilesParserParams() |
| params.removeHs = not keep_h if not keep_atom_map else False |
| mol = Chem.MolFromSmiles(s, params) |
|
|
| if add_h: |
| mol = Chem.AddHs(mol) |
|
|
| if keep_atom_map: |
| atom_map_numbers = tuple(atom.GetAtomMapNum() for atom in mol.GetAtoms()) |
| for idx, map_num in enumerate(atom_map_numbers): |
| if idx + 1 != map_num: |
| new_order = np.argsort(atom_map_numbers).tolist() |
| return Chem.rdmolops.RenumberAtoms(mol, new_order) |
|
|
| return mol |
|
|
| def generate_scaffold(mol: Union[str, Chem.Mol, Tuple[Chem.Mol, Chem.Mol]], include_chirality: bool = False) -> str: |
| """ |
| Computes the Bemis-Murcko scaffold for a SMILES string. |
| :param mol: A SMILES or an RDKit molecule. |
| :param include_chirality: Whether to include chirality in the computed scaffold.. |
| :return: The Bemis-Murcko scaffold for the molecule. |
| """ |
| if isinstance(mol, str): |
| mol = make_mol(mol, keep_h=False, add_h=False, keep_atom_map=False) |
| if isinstance(mol, tuple): |
| mol = mol[0] |
| scaffold = MurckoScaffold.MurckoScaffoldSmiles(mol=mol, includeChirality=include_chirality) |
|
|
| return scaffold |
|
|
|
|
| def scaffold_split(smi_list, num_sup, seed=1): |
| scaffold_dict = {} |
| for i, smi in enumerate(smi_list): |
| scaffold = generate_scaffold(smi) |
| if scaffold not in scaffold_dict: |
| scaffold_dict[scaffold] = [] |
| scaffold_dict[scaffold].append(i) |
| scaffold_id_list = [(k, v) for k, v in scaffold_dict.items()] |
| random.seed(seed) |
| random.shuffle(scaffold_id_list) |
| |
| |
| idx_list_all = [] |
| for scaffold, idx_list in scaffold_id_list: |
| idx_list_all += idx_list |
|
|
| return idx_list_all |
| |
|
|
| def is_older(_version): |
| if isinstance(_version, int): |
| return _version <= 25 |
| else: |
| dt1 = datetime.strptime("2019-03-01 00:00:00", "%Y-%m-%d %H:%M:%S") |
| dt2 = datetime.strptime(_version, "%Y-%m-%d %H:%M:%S") |
| return dt2 < dt1 |
|
|
| def read_cluster_file(cluster_file): |
| protein_clstr_dict = {} |
| with open(cluster_file) as f: |
| line_in_clstr = [] |
| for line in f.readlines(): |
| if line.startswith(">"): |
| for a in line_in_clstr: |
| for b in line_in_clstr: |
| if a not in protein_clstr_dict.keys(): |
| protein_clstr_dict[a] = [] |
| protein_clstr_dict[a].append(b) |
|
|
| line_in_clstr = [] |
| else: |
| line_in_clstr.append(line.split('|')[1]) |
| return protein_clstr_dict |
|
|
| @register_task("train_task") |
| class pocketscreen(UnicoreTask): |
| """Task for training transformer auto-encoder models.""" |
|
|
| @staticmethod |
| def add_args(parser): |
| """Add task-specific arguments to the parser.""" |
| parser.add_argument( |
| "data", |
| help="downstream data path", |
| ) |
| parser.add_argument( |
| "--finetune-mol-model", |
| default=None, |
| type=str, |
| help="pretrained molecular model path", |
| ) |
| parser.add_argument( |
| "--finetune-pocket-model", |
| default=None, |
| type=str, |
| help="pretrained pocket model path", |
| ) |
| parser.add_argument( |
| "--restore-model", |
| default=None, |
| type=str, |
| help="pretrained pocket model path", |
| ) |
| parser.add_argument( |
| "--dist-threshold", |
| type=float, |
| default=6.0, |
| help="threshold for the distance between the molecule and the pocket", |
| ) |
| parser.add_argument( |
| "--max-pocket-atoms", |
| type=int, |
| default=256, |
| help="selected maximum number of atoms in a pocket", |
| ) |
| parser.add_argument( |
| "--test-model", |
| default=False, |
| type=Boolean, |
| help="whether test model", |
| ) |
| parser.add_argument("--reg", action="store_true", help="regression task") |
| parser.add_argument( |
| "--few-shot", |
| default=False, |
| type=Boolean, |
| help="whether few-shot testing", |
| ) |
| parser.add_argument( |
| "--sup-num", |
| default=16, |
| type=float |
| ) |
| parser.add_argument( |
| "--valid-set", |
| default="CASF", |
| type=str |
| ) |
| parser.add_argument( |
| "--max-lignum", |
| type=int, |
| default=16 |
| ) |
| parser.add_argument( |
| "--test-max-lignum", |
| type=int, |
| default=512 |
| ) |
| parser.add_argument( |
| "--split-method", |
| type=str, |
| default="random", |
| help="split method for few-shot testing", |
| ) |
| parser.add_argument( |
| "--results-path", |
| type=str, |
| default=None, |
| help="results path for few-shot testing", |
| ) |
| parser.add_argument( |
| "--assay-idx", |
| type=int, |
| default=0 |
| ) |
| parser.add_argument( |
| "--contras-weight", |
| type=float, |
| default=0.5 |
| ) |
| parser.add_argument( |
| "--rank-weight", |
| type=float, |
| default=0.5 |
| ) |
| parser.add_argument( |
| "--protein-similarity-thres", |
| type=float, |
| default=1.0 |
| ) |
| parser.add_argument( |
| "--neg-margin", |
| type=float, |
| default=2.0 |
| ) |
| parser.add_argument( |
| "--demo-lig-file", |
| type=str, |
| default="" |
| ) |
| parser.add_argument( |
| "--demo-prot-file", |
| type=str, |
| default="" |
| ) |
| parser.add_argument( |
| "--demo-split-file", |
| type=str, |
| default="" |
| ) |
| parser.add_argument( |
| "--case-train-ligfile", |
| type=str, |
| default="" |
| ) |
| parser.add_argument( |
| "--case-test-ligfile", |
| type=str, |
| default="" |
| ) |
| parser.add_argument( |
| "--active-learning-resfile", |
| type=str, |
| default="" |
| ) |
|
|
| def __init__(self, args, dictionary, pocket_dictionary): |
| super().__init__(args) |
| self.dictionary = dictionary |
| self.pocket_dictionary = pocket_dictionary |
| self.seed = args.seed |
| |
| self.mask_idx = dictionary.add_symbol("[MASK]", is_special=True) |
| self.pocket_mask_idx = pocket_dictionary.add_symbol("[MASK]", is_special=True) |
| self.mol_reps = None |
| self.keys = None |
|
|
| def reduce_metrics(self, logging_outputs, loss, split='train'): |
| """Aggregate logging outputs from data parallel training.""" |
| from unicore import metrics, utils |
| bsz = sum(log.get("bsz", 0) for log in logging_outputs) |
| metrics.log_scalar("bsz", bsz, priority=190, round=1) |
|
|
| loss.__class__.reduce_metrics(logging_outputs, split, self.args) |
|
|
| @classmethod |
| def setup_task(cls, args, **kwargs): |
| mol_dictionary = Dictionary.load(os.path.join(PROJECT_ROOT, "vocab", "dict_mol.txt")) |
| pocket_dictionary = Dictionary.load(os.path.join(PROJECT_ROOT, "vocab", "dict_pkt.txt")) |
| logger.info("ligand dictionary: {} types".format(len(mol_dictionary))) |
| logger.info("pocket dictionary: {} types".format(len(pocket_dictionary))) |
| return cls(args, mol_dictionary, pocket_dictionary) |
|
|
| def load_few_shot_demo_dataset(self, split, **kwargs): |
| ligands_lmdb = os.path.join(self.args.demo_lig_file) |
| pocket_lmdb = os.path.join(self.args.demo_prot_file) |
| split_info = json.load(open(self.args.demo_split_file)) |
| import copy |
| pair_label = copy.deepcopy(split_info) |
|
|
| if split == "train": |
| pair_label["ligands"] = [lig for lig in split_info["train"]] |
| print("number of training ligands", len(pair_label["ligands"])) |
| else: |
| pair_label["ligands"] = [lig for lig in split_info["test"]] |
| print("number of testing ligands", len(pair_label["ligands"])) |
| pair_label["ligands"] = sorted(pair_label["ligands"], key=lambda x: x["act"], reverse=True) |
|
|
| pocket_dataset = self.load_pockets_dataset(pocket_lmdb, is_train=split=="train") |
| mol_dataset = self.load_mols_dataset(ligands_lmdb, "atoms", "coordinates", is_train=split=="train") |
| dataset = PairDataset(self.args, pocket_dataset, mol_dataset, [pair_label], split, use_cache=False) |
|
|
| self.datasets[split] = dataset |
| return dataset |
|
|
| def load_few_shot_TYK2_FEP_dataset(self, split, **kwargs): |
| test_datasets_root = os.path.join(PROJECT_ROOT, "test_datasets") |
| save_path = f"{test_datasets_root}/FEP" |
| ligands_lmdb = os.path.join(f"{test_datasets_root}/case_study/tyk2_fep_ligands.lmdb") |
| pocket_lmdb = os.path.join(f"{test_datasets_root}/FEP/proteins.lmdb") |
| pair_label_all = json.load(open(f"{test_datasets_root}/case_study/tyk2_fep.json")) |
| pair_label_all = [pair_label_all] |
| import pandas as pd |
|
|
| train_smiles = set(pd.read_csv(self.args.case_train_ligfile)['Smiles'].tolist()) |
| test_smiles = set(pd.read_csv(self.args.case_test_ligfile)['Smiles'].tolist()) |
|
|
| act_all = [] |
| avgact_train = 6.955628350893639 |
| for pair_label in pair_label_all: |
| pair_label["assay_id"] = "tyk2" |
| act_all += [x["act"] for x in pair_label["ligands"]] |
| if split == "train": |
| pair_label["ligands"] = [lig for lig in pair_label["ligands"] if lig["smi"] in train_smiles] |
| print("number of training ligands", len(pair_label["ligands"])) |
| else: |
| pair_label["ligands"] = [lig for lig in pair_label["ligands"] if lig["smi"] in test_smiles] |
| print("number of testing ligands", len(pair_label["ligands"])) |
| pair_label["ligands"] = sorted(pair_label["ligands"], key=lambda x: x["act"], reverse=True) |
|
|
| print("average activity of tyk2:", np.mean(act_all)) |
| print("moving the average to be", avgact_train) |
| for assay in pair_label_all: |
| for lig in assay["ligands"]: |
| lig["act"] = (lig["act"] - np.mean(act_all))/np.std(act_all) + avgact_train |
|
|
| pocket_dataset = self.load_pockets_dataset(pocket_lmdb, is_train=split=="train") |
| mol_dataset = self.load_mols_dataset(ligands_lmdb, "atoms", "coordinates", is_train=split=="train") |
| dataset = PairDataset(self.args, pocket_dataset, mol_dataset, pair_label_all, split, use_cache=False) |
|
|
| self.datasets[split] = dataset |
| return dataset |
|
|
| def load_few_shot_FEP_dataset(self, split, **kwargs): |
| data_path = os.path.join(PROJECT_ROOT, "test_datasets", "FEP") |
| ligands_lmdb = os.path.join(f"{data_path}/ligands.lmdb") |
| pocket_lmdb = os.path.join(f"{data_path}/proteins.lmdb") |
| pair_label_all = json.load(open(f"{data_path}/fep_labels.json")) |
|
|
| for pair_label in pair_label_all: |
| pair_label["assay_id"] = pair_label["uniprot"] |
| if self.args.sup_num < 1: |
| k_shot = int(self.args.sup_num * len(pair_label["ligands"])) |
| else: |
| k_shot = int(self.args.sup_num) |
| random.seed(self.args.seed) |
| random.shuffle(pair_label["ligands"]) |
| if split == "train": |
| pair_label["ligands"] = sorted(pair_label["ligands"][:k_shot], key=lambda x: x["act"], reverse=True) |
| else: |
| pair_label["ligands"] = sorted(pair_label["ligands"][k_shot:], key=lambda x: x["act"], reverse=True) |
|
|
| pocket_dataset = self.load_pockets_dataset(pocket_lmdb, is_train=split=="train") |
| mol_dataset = self.load_mols_dataset(ligands_lmdb, "atoms", "coordinates", is_train=split=="train") |
| dataset = PairDataset(self.args, pocket_dataset, mol_dataset, pair_label_all, split, use_cache=False) |
|
|
| self.datasets[split] = dataset |
| return dataset |
|
|
| def load_few_shot_ood_dataset(self, split, **kwargs): |
| data_path = os.path.join(PROJECT_ROOT, "test_datasets", "OOD") |
| mol_data_path = os.path.join(data_path, "oodtest_unit=%_lig.lmdb") |
| pocket_data_path = os.path.join(data_path, "oodtest_unit=%_prot.lmdb") |
| assay_test_ood = json.load(open(os.path.join(data_path, "oodtest_unit=%.json"))) |
| act_all = [] |
| avgact_train = 6.955628350893639 |
| for assay in assay_test_ood: |
| if self.args.sup_num < 1: |
| k_shot = int(self.args.sup_num * len(assay["ligands"])) |
| else: |
| k_shot = int(self.args.sup_num) |
|
|
| if self.args.split_method == "random": |
| select_index = list(range(len(assay["ligands"]))) |
| random.seed(self.args.seed) |
| random.shuffle(select_index) |
| elif self.args.split_method == "scaffold": |
| smi_list = [x["smi"] for x in assay["ligands"]] |
| select_index = scaffold_split(smi_list, k_shot) |
| else: |
| raise ValueError(f"Invalid split method: {self.args.split_method}. Supported methods are 'random' and 'scaffold'") |
|
|
| if split == "train": |
| assay["ligands"] = [assay["ligands"][idx] for idx in select_index[:k_shot]] |
| else: |
| assay["ligands"] = [assay["ligands"][idx] for idx in select_index[k_shot:]] |
| assay["ligands"] = [{"smi":x["smi"], "act":-x["act"]} for x in assay["ligands"]] |
| assay["ligands"] = sorted(assay["ligands"], key=lambda x: x["act"], reverse=True) |
| act_all += [x["act"] for x in assay["ligands"]] |
|
|
| print("average activity of ood:", np.mean(act_all)) |
| print("moving the average to be", avgact_train) |
| for assay in assay_test_ood: |
| for lig in assay["ligands"]: |
| lig["act"] = lig["act"] - np.mean(act_all) + avgact_train |
|
|
| pocket_dataset = self.load_pockets_dataset(pocket_data_path, is_train=split=="train") |
| mol_dataset = self.load_mols_dataset(mol_data_path, "atoms", "coordinates", is_train=split=="train") |
| dataset = PairDataset(self.args, pocket_dataset, mol_dataset, assay_test_ood, split, use_cache=False) |
|
|
| self.datasets[split] = dataset |
| return dataset |
|
|
|
|
| def load_few_shot_timesplit(self, split, **kwargs): |
| test_datasets_root = os.path.join(PROJECT_ROOT, "test_datasets") |
| mol_data_path = os.path.join(test_datasets_root, "TIME", "test_lig_timesplit.lmdb") |
| pocket_data_path = os.path.join(test_datasets_root, "TIME", "test_prot_timesplit.lmdb") |
| test_assays = json.load(open(os.path.join(test_datasets_root, "TIME", "assay_test_timesplit.json"))) |
| |
| print("number of test assays", len(test_assays)) |
| for assay in test_assays: |
| if self.args.sup_num < 1: |
| k_shot = int(self.args.sup_num * len(assay["ligands"])) |
| else: |
| k_shot = int(self.args.sup_num) |
|
|
| if self.args.split_method == "random": |
| select_index = list(range(len(assay["ligands"]))) |
| random.seed(self.args.seed) |
| random.shuffle(select_index) |
| elif self.args.split_method == "scaffold": |
| smi_list = [x["smi"] for x in assay["ligands"]] |
| select_index = scaffold_split(smi_list, k_shot, self.args.seed) |
| else: |
| raise ValueError(f"Invalid split method: {self.args.split_method}. Supported methods are 'random' and 'scaffold'") |
|
|
| if split == "train": |
| assay["ligands"] = [assay["ligands"][idx] for idx in select_index[:k_shot]] |
| else: |
| assay["ligands"] = [assay["ligands"][idx] for idx in select_index[k_shot:]] |
| assay["ligands"] = sorted(assay["ligands"], key=lambda x: x["act"], reverse=True) |
|
|
| pocket_dataset = self.load_pockets_dataset(pocket_data_path, is_train=split=="train") |
| mol_dataset = self.load_mols_dataset(mol_data_path, "atoms", "coordinates", is_train=split=="train") |
| dataset = PairDataset(self.args, pocket_dataset, mol_dataset, assay_test_unseen, split, use_cache=True) |
|
|
| self.datasets[split] = dataset |
| return dataset |
|
|
|
|
| def load_dataset(self, split, **kwargs): |
| """Load a given dataset split. |
| 'smi','pocket','atoms','coordinates','pocket_atoms','pocket_coordinates' |
| Args: |
| split (str): name of the data scoure (e.g., bppp) |
| """ |
|
|
| if self.args.few_shot: |
| if self.args.valid_set == "TYK2": |
| return self.load_few_shot_TYK2_FEP_dataset(split, **kwargs) |
| elif self.args.valid_set == "FEP": |
| return self.load_few_shot_FEP_dataset(split, **kwargs) |
| elif self.args.valid_set == "TIME": |
| return self.load_few_shot_timesplit(split, **kwargs) |
| elif self.args.valid_set == "OOD": |
| return self.load_few_shot_ood_dataset(split, **kwargs) |
| elif self.args.valid_set == "DEMO": |
| return self.load_few_shot_demo_dataset(split, **kwargs) |
|
|
| protein_clstr_dict = {} |
| if self.args.protein_similarity_thres == 0.4: |
| protein_clstr_dict_40 = read_cluster_file( |
| f"{self.args.data}/uniport40.clstr") |
| protein_clstr_dict = protein_clstr_dict_40 |
| elif self.args.protein_similarity_thres == 0.8: |
| protein_clstr_dict_80 = read_cluster_file( |
| f"{self.args.data}/uniport80.clstr") |
| protein_clstr_dict = protein_clstr_dict_80 |
|
|
| if split == "train" or (split == "valid" and self.args.valid_set == "TIME"): |
| mol_data_path = os.path.join(self.args.data, "train_lig_all_blend.lmdb") |
| pocket_data_path = os.path.join(self.args.data, "train_prot_all_blend.lmdb") |
| mol_dataset = self.load_mols_dataset(mol_data_path, "atoms", "coordinates", is_train=split=="train") |
| pocket_dataset = self.load_pockets_dataset(pocket_data_path, is_train=split=="train") |
| pair_label_1 = json.load(open(os.path.join(self.args.data, "train_label_pdbbind_seq.json"))) |
| pair_label_2 = json.load(open(os.path.join(self.args.data, "train_label_blend_seq_full.json"))) |
| test_datasets_root = os.path.join(PROJECT_ROOT, "test_datasets") |
| if self.args.valid_set == "TIME": |
| pair_label_2_new = [] |
| for assay in pair_label_2: |
| version = assay["version"] |
| version_older = is_older(version) |
| if version_older and split == "train": |
| pair_label_2_new.append(assay) |
| elif (not version_older) and split == "valid": |
| lig_new = [lig for lig in assay["ligands"] if lig["rel"] == "="] |
| assay["ligands"] = lig_new |
| if len(assay["ligands"]) >= 10: |
| pair_label_2_new.append(assay) |
| pair_label_2 = pair_label_2_new |
| else: |
| repeat_ligands = json.load(open(os.path.join(test_datasets_root, "fep_repeat_ligands_can.json"))) |
| if "no_similar_ligand" in self.args.save_dir: |
| sim_ligands_cache = os.path.join(test_datasets_root, "fep_similar_ligands_0d5.json") |
| repeat_ligands += json.load(open(sim_ligands_cache)) |
|
|
| pair_label_2_new = [] |
| repeat_ligands = set(repeat_ligands) |
| print("number of deleted ligands", len(repeat_ligands)) |
| for assay in pair_label_2: |
| ligands_new = [] |
| for lig in assay["ligands"]: |
| if lig["smi"] not in repeat_ligands: |
| ligands_new.append(lig) |
| if len(ligands_new) >= 3: |
| assay["ligands"] = ligands_new |
| pair_label_2_new.append(assay) |
| print("number of assays before processing :", len(pair_label_2)) |
| pair_label_2 = pair_label_2_new |
| print("number of assays after remove ligands in FEP:", len(pair_label_2)) |
|
|
| non_repeat_assayids = json.load(open(os.path.join(test_datasets_root, "fep_assay_ids.json"))) |
| non_repeat_assayids = set(non_repeat_assayids) |
|
|
| pair_label_2 = [x for x in pair_label_2 if (x["assay_id"] not in non_repeat_assayids)] |
| print("number of assays after remove assays in FEP:", len(pair_label_2)) |
|
|
| if self.args.valid_set == "CASF": |
| |
| testset_uniprot_lst = [] |
| testset_uniprot_lst += [x[0] for x in json.load(open(f"{test_datasets_root}/dude.json"))] |
| testset_uniprot_lst += [x[0] for x in json.load(open(f"{test_datasets_root}/PCBA.json"))] |
| testset_uniprot_lst += [x[0] for x in json.load(open(f"{test_datasets_root}/dekois.json"))] |
|
|
| |
| if "no_similar_protein" in self.args.save_dir: |
| testset_uniprot_lst_new = [] |
| for uniprot in testset_uniprot_lst: |
| testset_uniprot_lst_new += protein_clstr_dict.get(uniprot, [uniprot]) |
| testset_uniprot_lst_new.append(uniprot) |
| testset_uniprot_lst = testset_uniprot_lst_new |
| print(testset_uniprot_lst) |
| elif self.args.valid_set == "FEP": |
| |
| testset_uniprot_lst = [] |
|
|
| |
| if "no_similar_protein" in self.args.save_dir: |
| testset_uniprot_lst += [x[0] for x in json.load(open(f"{test_datasets_root}/FEP.json"))] |
| testset_uniprot_lst_new = [] |
| for uniprot in testset_uniprot_lst: |
| testset_uniprot_lst_new += protein_clstr_dict.get(uniprot, [uniprot]) |
| testset_uniprot_lst_new.append(uniprot) |
| testset_uniprot_lst = testset_uniprot_lst_new |
| print(testset_uniprot_lst) |
| else: |
| testset_uniprot_lst = [] |
|
|
| pair_label_2 = [x for x in pair_label_2 if (x["uniprot"] not in testset_uniprot_lst)] |
| print("number of assay after remove test uniport:", len(pair_label_2)) |
|
|
| |
| |
| if "no_similar_protein" in self.args.save_dir: |
| old_len = len(pair_label_1) |
| pair_label_1 = [x for x in pair_label_1 if (x["uniprot"] not in testset_uniprot_lst)] |
| print("number of deleted pdbbind after remove test uniport", old_len - len(pair_label_1)) |
|
|
| print(f"totally {len(pair_label_2)} datas (blend ChEMBL and BindingDB) for {split}") |
| pair_label = pair_label_2 |
| if split == "train": |
| pair_label += pair_label_1 |
| pair_dataset = PairDataset(self.args, pocket_dataset, mol_dataset, pair_label, split=split, use_cache=True, cache_dir=self.args.data) |
| elif split == "valid" and self.args.valid_set == "CASF": |
| |
| split_old = split |
| split = "valid" |
| mol_data_path = os.path.join(self.args.data, split + "_lig.lmdb") |
| pocket_data_path = os.path.join(self.args.data, split + "_prot.lmdb") |
| pair_label = json.load(open(os.path.join(self.args.data, split + "_label_seq.json"))) |
| split = split_old |
| mol_dataset = self.load_mols_dataset(mol_data_path, "atoms", "coordinates") |
| pocket_dataset = self.load_pockets_dataset(pocket_data_path) |
| pair_dataset = PairDataset(self.args, pocket_dataset, mol_dataset, pair_label, split, use_cache=False) |
| elif split == "valid" and self.args.valid_set == "FEP": |
| |
| save_path = os.path.join(PROJECT_ROOT, "test_datasets", "FEP") |
| mol_data_path = os.path.join(f"{save_path}/ligands.lmdb") |
| pocket_data_path = os.path.join(f"{save_path}/proteins.lmdb") |
| pair_label = json.load(open(f"{save_path}/fep_labels.json")) |
| mol_dataset = self.load_mols_dataset(mol_data_path, "atoms", "coordinates") |
| pocket_dataset = self.load_pockets_dataset(pocket_data_path) |
| pair_dataset = PairDataset(self.args, pocket_dataset, mol_dataset, pair_label, split, use_cache=False) |
|
|
|
|
|
|
| if split == "train": |
| with data_utils.numpy_seed(self.args.seed): |
| shuffle = np.random.permutation(len(pair_dataset)) |
|
|
| self.datasets[split] = SortDataset( |
| pair_dataset, |
| sort_order=[shuffle], |
| ) |
| self.datasets[split] = ResamplingDataset( |
| self.datasets[split] |
| ) |
| else: |
| self.datasets[split] = pair_dataset |
| return pair_dataset |
|
|
| def load_mols_dataset(self, data_path, atoms, coords, **kwargs): |
| dataset = LMDBDataset(data_path) |
| |
| dataset = AffinityMolDataset( |
| dataset, |
| self.args.seed, |
| atoms, |
| coords, |
| is_train=kwargs.get("is_train", False), |
| ) |
|
|
| smi_dataset = KeyDataset(dataset, "smi") |
| if kwargs.get("load_name", False): |
| name_dataset = KeyDataset(dataset, "name") |
|
|
| def PrependAndAppend(dataset, pre_token, app_token): |
| dataset = PrependTokenDataset(dataset, pre_token) |
| return AppendTokenDataset(dataset, app_token) |
|
|
| dataset = RemoveHydrogenDataset(dataset, "atoms", "coordinates", True, True) |
| dataset = CroppingDataset( |
| dataset, |
| self.seed, |
| atoms, |
| coords, |
| self.args.max_pocket_atoms//4, |
| ) |
| apo_dataset = NormalizeDataset(dataset, "coordinates") |
|
|
| src_dataset = KeyDataset(apo_dataset, "atoms") |
| len_dataset = LengthDataset(src_dataset) |
| src_dataset = TokenizeDataset( |
| src_dataset, self.dictionary, max_seq_len=self.args.max_seq_len |
| ) |
| coord_dataset = KeyDataset(apo_dataset, "coordinates") |
| src_dataset = PrependAndAppend( |
| src_dataset, self.dictionary.bos(), self.dictionary.eos() |
| ) |
| edge_type = EdgeTypeDataset(src_dataset, len(self.dictionary)) |
| coord_dataset = FromNumpyDataset(coord_dataset) |
| distance_dataset = DistanceDataset(coord_dataset) |
| coord_dataset = PrependAndAppend(coord_dataset, 0.0, 0.0) |
| distance_dataset = PrependAndAppend2DDataset(distance_dataset, 0.0) |
|
|
| in_datasets = { |
| "net_input": { |
| "mol_src_tokens": RightPadDataset( |
| src_dataset, |
| pad_idx=self.dictionary.pad(), |
| ), |
| "mol_src_distance": RightPadDataset2D( |
| distance_dataset, |
| pad_idx=0, |
| ), |
| "mol_src_edge_type": RightPadDataset2D( |
| edge_type, |
| pad_idx=0, |
| ), |
| }, |
| "smi_name": RawArrayDataset(smi_dataset), |
| |
| "mol_len": RawArrayDataset(len_dataset), |
| } |
| if kwargs.get("load_name", False): |
| in_datasets["name"] = name_dataset |
|
|
| nest_dataset = NestedDictionaryDataset(in_datasets) |
| return nest_dataset |
|
|
| def load_pockets_dataset(self, data_path, **kwargs): |
|
|
| dataset = LMDBDataset(data_path) |
|
|
| dataset = AffinityPocketDataset( |
| dataset, |
| self.args.seed, |
| "pocket_atoms", |
| "pocket_coordinates", |
| is_train=kwargs.get("is_train", False), |
| pocket="pocket" |
| ) |
| poc_dataset = KeyDataset(dataset, "pocket") |
|
|
| def PrependAndAppend(dataset, pre_token, app_token): |
| dataset = PrependTokenDataset(dataset, pre_token) |
| return AppendTokenDataset(dataset, app_token) |
|
|
| dataset = RemoveHydrogenPocketDataset( |
| dataset, |
| "pocket_atoms", |
| "pocket_coordinates", |
| True, |
| True, |
| ) |
| dataset = CroppingPocketDataset( |
| dataset, |
| self.seed, |
| "pocket_atoms", |
| "pocket_coordinates", |
| self.args.max_pocket_atoms, |
| ) |
|
|
| apo_dataset = NormalizeDataset(dataset, "pocket_coordinates") |
|
|
| src_pocket_dataset = KeyDataset(apo_dataset, "pocket_atoms") |
| len_dataset = LengthDataset(src_pocket_dataset) |
| src_pocket_dataset = TokenizeDataset( |
| src_pocket_dataset, |
| self.pocket_dictionary, |
| max_seq_len=self.args.max_seq_len, |
| ) |
| coord_pocket_dataset = KeyDataset(apo_dataset, "pocket_coordinates") |
| src_pocket_dataset = PrependAndAppend( |
| src_pocket_dataset, |
| self.pocket_dictionary.bos(), |
| self.pocket_dictionary.eos(), |
| ) |
| pocket_edge_type = EdgeTypeDataset( |
| src_pocket_dataset, len(self.pocket_dictionary) |
| ) |
| coord_pocket_dataset = FromNumpyDataset(coord_pocket_dataset) |
| distance_pocket_dataset = DistanceDataset(coord_pocket_dataset) |
| coord_pocket_dataset = PrependAndAppend(coord_pocket_dataset, 0.0, 0.0) |
| distance_pocket_dataset = PrependAndAppend2DDataset( |
| distance_pocket_dataset, 0.0 |
| ) |
|
|
| nest_dataset = NestedDictionaryDataset( |
| { |
| "net_input": { |
| "pocket_src_tokens": RightPadDataset( |
| src_pocket_dataset, |
| pad_idx=self.pocket_dictionary.pad(), |
| ), |
| "pocket_src_distance": RightPadDataset2D( |
| distance_pocket_dataset, |
| pad_idx=0, |
| ), |
| "pocket_src_edge_type": RightPadDataset2D( |
| pocket_edge_type, |
| pad_idx=0, |
| ), |
| "pocket_src_coord": RightPadDatasetCoord( |
| coord_pocket_dataset, |
| pad_idx=0, |
| ), |
| }, |
| "pocket_name": RawArrayDataset(poc_dataset), |
| "pocket_len": RawArrayDataset(len_dataset), |
| }, |
| ) |
| return nest_dataset |
|
|
| def build_model(self, args): |
| from unicore import models |
|
|
| model = models.build_model(args, self) |
|
|
| if args.finetune_mol_model is not None: |
| print("load pretrain model weight from...", args.finetune_mol_model) |
| state = checkpoint_utils.load_checkpoint_to_cpu( |
| args.finetune_mol_model, |
| ) |
| model.mol_model.load_state_dict(state["model"], strict=False) |
|
|
| if args.finetune_pocket_model is not None: |
| print("load pretrain model weight from...", args.finetune_pocket_model) |
| state = checkpoint_utils.load_checkpoint_to_cpu( |
| args.finetune_pocket_model, |
| ) |
| model.pocket_model.load_state_dict(state["model"], strict=False) |
|
|
| if args.restore_model is not None: |
| print("load pretrain model weight from...", args.restore_model) |
| state = checkpoint_utils.load_checkpoint_to_cpu( |
| args.restore_model, |
| ) |
| model.load_state_dict(state["model"], strict=False) |
|
|
| return model |
|
|
| def train_step( |
| self, sample, model, loss, optimizer, update_num, ignore_grad=False |
| ): |
| """ |
| Do forward and backward, and return the loss as computed by *loss* |
| for the given *model* and *sample*. |
| |
| Args: |
| sample (dict): the mini-batch. The format is defined by the |
| :class:`~unicore.data.UnicoreDataset`. |
| model (~unicore.models.BaseUnicoreModel): the model |
| loss (~unicore.losses.UnicoreLoss): the loss |
| optimizer (~unicore.optim.UnicoreOptimizer): the optimizer |
| update_num (int): the current update |
| ignore_grad (bool): multiply loss by 0 if this is set to True |
| |
| Returns: |
| tuple: |
| - the loss |
| - the sample size, which is used as the denominator for the |
| gradient |
| - logging outputs to display while training |
| """ |
|
|
| model.train() |
| model.set_num_updates(update_num) |
| with torch.autograd.profiler.record_function("forward"): |
| loss, sample_size, logging_output = loss(model, sample) |
| if ignore_grad: |
| loss *= 0 |
| |
| with torch.autograd.profiler.record_function("backward"): |
| optimizer.backward(loss) |
| return loss, sample_size, logging_output |
|
|
| def valid_step(self, sample, model, loss, test=False): |
| model.eval() |
| with torch.no_grad(): |
| loss, sample_size, logging_output = loss(model, sample) |
| return loss, sample_size, logging_output |
|
|