Home▸Articles▸Target Modeling

Drug-Target Interaction Prediction

Predict whether a small molecule binds a protein target and estimate binding affinity. Covers binary DTI vs. affinity regression, drug and protein representations, DeepDTA, GraphDTA, TransformerCPI, cold-start challenges, and concordance index evaluation.

mysimulator teamUpdated January 2026≈ 11 min read▶ Open the simulation

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:

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:

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

For Binding Affinity Regression

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:

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

  1. Start with the Davis or KIBA TDC benchmark using the official random, cold-drug, and cold-protein splits.
  2. 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.
  3. Implement DeepDTA (1D CNN on SMILES + 1D CNN on sequence). Should improve warm split CI to 0.80-0.85.
  4. Optionally replace the protein encoder with frozen ESM-2 embeddings. This typically gives +0.03-0.05 CI improvement on cold splits.
  5. Report all three split types (warm, cold-drug, cold-protein) to enable fair comparison with literature.

Further Reading

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

What did you find?

Add reproduction steps (optional)