Drug-target interaction (DTI) prediction is the task of computationally determining whether a small molecule drug candidate will bind to a specific protein target, and with what affinity. It sits at the intersection of cheminformatics and structural bioinformatics, requiring representations of both molecular chemistry and protein biology.
DTI prediction is foundational to target-based drug discovery. Given a disease-relevant protein (e.g., a kinase mutated in cancer, or a GPCR involved in a metabolic disorder), DTI models can screen large compound libraries to identify binding candidates, predict selectivity across related protein family members, and estimate binding affinities to guide lead optimization. Modern DTI models have also been applied to drug repurposing: identifying existing approved drugs that might bind novel targets relevant to emerging diseases.
Problem Formulation
DTI prediction can be framed in two ways depending on the application:
Binary Interaction Prediction
Given a (drug, protein) pair, predict whether the drug binds the protein (1) or not (0). This is a classification task. A binding threshold (typically Kd, IC50, or Kd below 10 uM) converts continuous affinity measurements to binary labels. The positive class is typically defined by interaction databases (DrugBank, STITCH) where known drug-target pairs are treated as positives and random pairings as negatives.
Challenges: negative examples are not truly confirmed non-interactions (absence of evidence is not evidence of absence in DTI databases), leading to noisy labels.
Binding Affinity Regression
Given a (drug, protein) pair, predict the binding affinity value: Kd, Ki, IC50, or pKd (converted to log scale). This is a regression task that provides richer information for lead optimization. Datasets like BindingDB, Davis kinase dataset (442 kinases, 68 drugs), and KIBA dataset (229 kinases, 2111 drugs) provide continuous affinity measurements.
The regression formulation is preferred for lead optimization because small differences in predicted affinity matter (distinguishing a 1 nM binder from a 10 nM binder). For large-scale screening, binary prediction is often sufficient to prioritize candidates.
Data Sources
| Database | Content | Size | Task Type |
|---|---|---|---|
| BindingDB | Experimental binding affinities (Ki, Kd, IC50) | 2.9M+ measurements, 1.2M compounds, 9,000+ proteins | Regression |
| DrugBank | Approved drug-target associations with mechanism | 14,000+ drugs, 5,200+ targets | Binary (interaction / no interaction) |
| STITCH | Chemical-protein interactions including indirect | 500K chemicals, 9.6M+ interactions | Binary with confidence scores |
| ChEMBL | Curated bioactivity data from literature | 19M+ activities, 15,000+ targets | Regression (IC50, Ki, pIC50) |
| Davis (TDC) | Kinase inhibitor selectivity panel | 68 drugs x 442 kinases = 30,056 pairs | Regression (Kd in nM) |
| KIBA (TDC) | Integrated kinase-inhibitor bioactivity | 2,111 drugs x 229 kinases = 118,254 pairs | Regression (KIBA score) |
Drug Representation
Three main approaches for representing the drug molecule:
- Molecular fingerprints: Fast, fixed-length, works with any ML architecture. ECFP4 (2048 bits) or MACCS keys. Standard in classical DTI models and as baselines.
- SMILES as sequence: Use the SMILES string directly as a character-level or token-level sequence input to a CNN or Transformer. DeepDTA uses a 1D CNN on SMILES characters.
- Molecular graphs: Atoms as nodes, bonds as edges, processed by a GNN. GraphDTA uses a GNN to encode the drug molecule, enabling message passing across the molecular structure.
The choice depends on model architecture. For a simple concatenate-and-predict approach, fingerprints are easiest. For end-to-end models that can learn task-specific drug representations, SMILES-based CNNs or GNNs are preferred.
Protein Representation
Protein representation is the more complex side of DTI modeling. Several approaches are used:
- Amino acid sequence (1D): The protein is represented as its amino acid sequence string. A 1D CNN or RNN processes the sequence character by character. DeepDTA uses this approach. The advantage is that sequence data is universally available (UniProt has 250M+ sequences). The limitation is that sequence alone does not capture 3D structure or the binding pocket geometry directly.
- Pre-trained protein language model embeddings: ESM-2 (Meta), ProtBERT, or Ankh provide deep embeddings of amino acid sequences that encode functional and structural information learned from billions of protein sequences. These embeddings dramatically improve performance on most DTI benchmarks and are now the standard representation for sequence-based DTI models.
- Protein contact graphs / structure graphs: If a 3D structure is available (PDB or AlphaFold2 prediction), the protein can be represented as a graph where residues are nodes and edges connect residues within a distance threshold (typically 8-12 Angstroms). GraphDTA and MGraphDTA use GNNs on protein structure graphs.
- Binding site descriptors: If the binding pocket is known, encode only the local pocket environment rather than the full protein. This reduces noise and focuses the model on the interaction interface.
ML Models for DTI
| Model | Drug Rep. | Protein Rep. | Architecture | Task |
|---|---|---|---|---|
| DeepDTA | SMILES (1D CNN) | AA sequence (1D CNN) | Concatenate + FC layers | Regression (Kd) |
| GraphDTA | Molecular graph (GCN/GAT) | AA sequence (1D CNN) | GNN + FC layers | Regression (Kd, KIBA) |
| TransformerCPI | SMILES tokens | AA sequence | Cross-attention Transformer | Binary interaction |
| MolTrans | SMILES substructures | AA subsequences | Interaction-aware Transformer | Binary interaction |
| ESM-DTI | Molecular fingerprints or GNN | ESM-2 protein LM embeddings | Shallow MLP on concatenated embeddings | Both |
Code Example: Simple DTI Model with Sequence and Fingerprint Features
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
import numpy as np
import pandas as pd
from rdkit import Chem
from rdkit.Chem import AllChem
# -------------------------------------------------------
# 1. Featurization
# -------------------------------------------------------
AA_VOCAB = {aa: i+1 for i, aa in enumerate('ACDEFGHIKLMNPQRSTVWY')}
AA_VOCAB['X'] = 21 # Unknown amino acid
MAX_PROT_LEN = 1200
MAX_DRUG_LEN = 100
def encode_protein(seq: str, max_len: int = MAX_PROT_LEN) -> np.ndarray:
"""Encode protein amino acid sequence as integer indices."""
indices = [AA_VOCAB.get(aa, 21) for aa in seq.upper()[:max_len]]
# Pad to max_len
indices += [0] * (max_len - len(indices))
return np.array(indices, dtype=np.int32)
def encode_drug_smiles(smiles: str, max_len: int = MAX_DRUG_LEN) -> np.ndarray:
"""Encode SMILES as character indices."""
SMILES_VOCAB = {c: i+1 for i, c in enumerate(
'CNOSPFClBrIchons()[]=#-+@./\\1234567890%'
)}
indices = [SMILES_VOCAB.get(c, 0) for c in smiles[:max_len]]
indices += [0] * (max_len - len(indices))
return np.array(indices, dtype=np.int32)
def drug_fingerprint(smiles: str) -> np.ndarray:
"""Morgan ECFP4 fingerprint as float32 numpy array."""
mol = Chem.MolFromSmiles(smiles)
if mol is None:
return np.zeros(1024, dtype=np.float32)
fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius=2, nBits=1024)
arr = np.zeros(1024, dtype=np.float32)
fp.ToNumpyArray(arr)
return arr
# -------------------------------------------------------
# 2. DTI Dataset
# -------------------------------------------------------
class DTIDataset(Dataset):
"""Dataset for drug-target interaction: drug SMILES + protein sequence + label."""
def __init__(self, drug_smiles: list, protein_seqs: list, labels: list):
self.drugs = [drug_fingerprint(s) for s in drug_smiles]
self.proteins = [encode_protein(p) for p in protein_seqs]
self.labels = labels
def __len__(self):
return len(self.labels)
def __getitem__(self, idx):
return {
'drug': torch.tensor(self.drugs[idx], dtype=torch.float32),
'protein': torch.tensor(self.proteins[idx], dtype=torch.long),
'label': torch.tensor(self.labels[idx], dtype=torch.float32),
}
# -------------------------------------------------------
# 3. DeepDTA-inspired model (fingerprint + sequence CNN)
# -------------------------------------------------------
class DTIModel(nn.Module):
"""
Drug-target interaction model:
- Drug: Morgan fingerprint -> 2-layer MLP
- Protein: Embedding + 3-layer 1D CNN
- Combined: FC layers for binding affinity regression
"""
def __init__(self, drug_dim: int = 1024, prot_vocab_size: int = 22,
prot_embed_dim: int = 128, cnn_filters: int = 32,
hidden_dim: int = 256, dropout: float = 0.1):
super().__init__()
# Drug encoder: MLP on fingerprint
self.drug_encoder = nn.Sequential(
nn.Linear(drug_dim, 512),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(512, 256),
nn.ReLU(),
)
# Protein encoder: embedding + stacked 1D CNNs
self.prot_embed = nn.Embedding(prot_vocab_size, prot_embed_dim, padding_idx=0)
self.prot_cnn = nn.Sequential(
nn.Conv1d(prot_embed_dim, cnn_filters, kernel_size=4),
nn.ReLU(),
nn.Conv1d(cnn_filters, cnn_filters * 2, kernel_size=8),
nn.ReLU(),
nn.Conv1d(cnn_filters * 2, cnn_filters * 3, kernel_size=12),
nn.ReLU(),
)
# Combined interaction layers
# Drug output: 256-dim, Protein output: global max pool over channels
combined_dim = 256 + cnn_filters * 3
self.interaction = nn.Sequential(
nn.Linear(combined_dim, 1024),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(1024, 512),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(512, 1),
)
def forward(self, drug: torch.Tensor, protein: torch.Tensor) -> torch.Tensor:
# Drug embedding
drug_emb = self.drug_encoder(drug) # (batch, 256)
# Protein CNN
prot_emb = self.prot_embed(protein) # (batch, seq_len, embed_dim)
prot_emb = prot_emb.permute(0, 2, 1) # (batch, embed_dim, seq_len)
prot_cnn = self.prot_cnn(prot_emb) # (batch, filters, reduced_len)
prot_pool = prot_cnn.max(dim=2).values # Global max pool: (batch, filters)
# Concatenate and predict
combined = torch.cat([drug_emb, prot_pool], dim=1)
return self.interaction(combined).squeeze(-1)
# -------------------------------------------------------
# 4. Training on Davis dataset via TDC
# -------------------------------------------------------
def train_dti_model(df_train, df_val, epochs=50, lr=1e-3):
"""
df_train and df_val should have columns: Drug, Target, Y
Drug: SMILES, Target: amino acid sequence, Y: binding affinity
"""
train_set = DTIDataset(
df_train['Drug'].tolist(),
df_train['Target'].tolist(),
df_train['Y'].tolist()
)
val_set = DTIDataset(
df_val['Drug'].tolist(),
df_val['Target'].tolist(),
df_val['Y'].tolist()
)
train_loader = DataLoader(train_set, batch_size=64, shuffle=True)
val_loader = DataLoader(val_set, batch_size=64)
model = DTIModel()
optimizer = optim.Adam(model.parameters(), lr=lr)
criterion = nn.MSELoss()
best_val_mse = float('inf')
for epoch in range(1, epochs + 1):
model.train()
for batch in train_loader:
out = model(batch['drug'], batch['protein'])
loss = criterion(out, batch['label'])
optimizer.zero_grad()
loss.backward()
optimizer.step()
# Validation
model.eval()
val_preds, val_labels = [], []
with torch.no_grad():
for batch in val_loader:
preds = model(batch['drug'], batch['protein'])
val_preds.append(preds)
val_labels.append(batch['label'])
val_preds = torch.cat(val_preds)
val_labels = torch.cat(val_labels)
val_mse = ((val_preds - val_labels) ** 2).mean().item()
val_rmse = val_mse ** 0.5
if epoch % 10 == 0:
print(f"Epoch {epoch:3d}: val_RMSE={val_rmse:.4f}")
if val_mse < best_val_mse:
best_val_mse = val_mse
torch.save(model.state_dict(), 'best_dti_model.pt')
return model
# Load Davis dataset from TDC
# from tdc.multi_pred import DTI
# data = DTI(name='Davis')
# split = data.get_split()
# model = train_dti_model(split['train'], split['valid'])
Evaluation: AUROC, AUPRC, and Concordance Index
For Binary DTI Classification
- AUROC: Measures how well the model ranks true drug-target pairs above random (drug, protein) pairs. Standard metric for interaction prediction.
- AUPRC: Average precision, more informative for imbalanced datasets where true interactions are rare compared to the full cross-product of drugs and proteins.
For Binding Affinity Regression
- MSE / RMSE: Root mean squared error on the affinity scale (e.g., pKd or log IC50). Primary loss function during training.
- Pearson r: Measures linear correlation between predicted and actual affinities. Does not penalize systematic bias.
- Concordance Index (CI): The probability that two randomly chosen drug-target pairs with different affinities are correctly ranked by the model. Analogous to AUROC but for regression. The primary metric for Davis and KIBA benchmarks. CI=1 means perfect ordering; CI=0.5 is random.
- Spearman rho: Rank correlation, which is robust to outliers and measures whether the model correctly ranks pairs by affinity.
import numpy as np
from itertools import combinations
def concordance_index(y_true: np.ndarray, y_pred: np.ndarray) -> float:
"""
Compute concordance index (Harrell's C-index) for binding affinity regression.
CI = P(pred(i) > pred(j) | true(i) > true(j))
CI = 0.5 is random, CI = 1.0 is perfect ranking.
"""
n = 0
concordant = 0
for i, j in combinations(range(len(y_true)), 2):
if y_true[i] != y_true[j]:
n += 1
if (y_true[i] > y_true[j] and y_pred[i] > y_pred[j]):
concordant += 1
elif (y_true[i] < y_true[j] and y_pred[i] < y_pred[j]):
concordant += 1
return concordant / n if n > 0 else 0.0
# Note: For large datasets, use the lifelines package for efficient CI computation
# from lifelines.utils import concordance_index as ci
# ci(y_true, y_pred)
Challenges: Data Leakage, Cold-Start, and Transductive vs. Inductive
Data Leakage in DTI Benchmarks
The most dangerous pitfall in DTI evaluation is the splitting strategy. A random split of (drug, protein, label) tuples will place analogs of training drugs or closely related protein family members in the test set. This allows the model to interpolate within known chemical series and known protein families, giving optimistically high CI and AUROC values that do not reflect real prospective performance.
Proper DTI evaluation uses cold-start splits:
- Cold drug split: All (drug, protein) pairs for a given drug are in either train or test. Tests generalization to new drugs unseen during training.
- Cold protein split: All pairs for a given protein are in train or test. Tests generalization to new protein targets.
- Cold drug-protein split: Both drug and protein are unseen. The most challenging and realistic scenario for de novo DTI prediction.
The performance drop from warm to cold splits is typically 0.10-0.20 CI units. Papers reporting only warm-split performance are measuring memorization, not generalization.
Cold-Start Problem
In the cold-start setting, the model must predict the affinity of a completely novel drug against a completely novel protein. This requires the model to learn a general understanding of drug-protein complementarity from the training distribution, rather than interpolating between similar known pairs. Current DTI models struggle with this: state-of-the-art CI on cold drug-protein splits is typically 0.55-0.65, barely above random.
Pre-trained molecular and protein language models (ESM-2 for proteins, ChemBERTa for drugs) improve cold-start performance by providing representations that encode functional and structural information even for unseen sequences.
Transductive vs. Inductive Settings
Many DTI benchmark papers report results in a transductive setting: drugs and proteins appearing in the test set also appear in the training set (just with different interaction labels). This is not a realistic deployment scenario. The inductive setting, where test drugs or proteins are entirely absent from training, is the correct evaluation for a model intended to generalize to novel compounds or targets.
Practical Workflow Recommendation
- Start with the Davis or KIBA TDC benchmark using the official random, cold-drug, and cold-protein splits.
- Implement a baseline: ECFP4 fingerprint (drug) + amino acid composition descriptor (protein) + gradient boosting regressor. This should achieve CI around 0.70-0.75 on warm split and 0.60-0.65 on cold-drug split.
- Implement DeepDTA (1D CNN on SMILES + 1D CNN on sequence). Should improve warm split CI to 0.80-0.85.
- Optionally replace the protein encoder with frozen ESM-2 embeddings. This typically gives +0.03-0.05 CI improvement on cold splits.
- Report all three split types (warm, cold-drug, cold-protein) to enable fair comparison with literature.
Further Reading
- Ozturk et al. (2018): DeepDTA
- Jiang et al. (2021): GraphDTA
- TDC DTI Benchmarks (Davis, KIBA)
- BindingDB Database
- DrugBank
- ESMAtlas (ESM-2 Protein Embeddings)
- Improving DTI with cold-start evaluation (Hua et al. 2021)
Try it live
Explore a related model hands-on: open Graph Neural Network Target Prioritization Ranking and change its parameters while it runs. Everything stays in your browser.
▶ Open Graph Neural Network Target Prioritization Ranking simulation