AI药物发现与分子生成完全教程

教程简介

本教程全面讲解AI药物发现与分子生成的核心技术,涵盖分子表示学习(SMILES/分子图)、分子属性预测(GNN)、分子生成模型(VAE/Flow/Diffusion)、蛋白质结构预测(AlphaFold)、药物-靶点预测、虚拟筛选、分子动力学模拟等核心内容,帮助开发者掌握AI制药技术。

AI药物发现与分子生成完全教程

1. AI药物发现概述与技术栈

传统药物研发平均耗时 10-15 年,耗资超过 26 亿美元,且成功率不足 10%。AI 正在从根本上改变这一格局——通过计算方法加速靶点发现、先导化合物筛选和临床试验设计,将部分环节的时间从数年缩短到数周。

AI 药物发现的核心技术栈包括:

  • 分子表示学习:将化学分子转化为机器可处理的向量/图结构
  • 分子属性预测:预测溶解度、毒性、生物活性等理化性质
  • 分子生成与优化:设计具有目标属性的全新分子
  • 蛋白质结构预测:预测靶点蛋白的三维结构
  • 药物-靶点相互作用:预测分子与蛋白质的结合能力
  • 虚拟筛选:从大规模化合物库中高效筛选候选药物

核心工具生态包括 RDKit(化学信息学)、PyTorch Geometric(图神经网络)、DeepChem(药物发现专用框架)和 Hugging Face 的分子模型库。

# 环境准备(核心依赖)
# pip install rdkit-pypi torch-geometric deepchem

from rdkit import Chem
from rdkit.Chem import Draw, Descriptors, AllChem
import numpy as np

# 用RDKit加载一个分子(阿司匹林)
aspirin_smiles = 'CC(=O)Oc1ccccc1C(=O)O'
mol = Chem.MolFromSmiles(aspirin_smiles)
print(f'分子式: {Chem.rdMolDescriptors.CalcMolFormula(mol)}')
print(f'分子量: {Descriptors.MolWt(mol):.2f}')
print(f'LogP: {Descriptors.MolLogP(mol):.2f}')
print(f'氢键供体: {Descriptors.NumHDonors(mol)}')
print(f'氢键受体: {Descriptors.NumHAcceptors(mol)}')

2. 分子表示学习

2.1 SMILES 字符串表示

SMILES(Simplified Molecular Input Line Entry System)是一种将分子结构编码为 ASCII 字符串的线性表示法。它简洁高效,可以作为序列模型(如 Transformer)的输入。

from rdkit import Chem

# 常见分子的SMILES
molecules = {
    '阿司匹林': 'CC(=O)Oc1ccccc1C(=O)O',
    '布洛芬': 'CC(C)Cc1ccc(cc1)C(C)C(=O)O',
    '对乙酰氨基酚': 'CC(=O)Nc1ccc(O)cc1',
    '咖啡因': 'Cn1c(=O)c2c(ncn2C)n(C)c1=O',
}

# SMILES验证:解析后重新生成规范化SMILES
for name, smi in molecules.items():
    mol = Chem.MolFromSmiles(smi)
    canonical = Chem.MolToSmiles(mol)
    print(f'{name}: {canonical}')

SELFIES(Self-Referencing Embedded Strings) 是一种更鲁棒的分子表示法,任意字符串都能解码为合法分子,这对生成模型尤为重要。

# pip install selfies
import selfies as sf

# SMILES -> SELFIES
aspirin_smiles = 'CC(=O)Oc1ccccc1C(=O)O'
aspirin_selfies = sf.encoder(aspirin_smiles)
print(f'SELFIES: {aspirin_selfies}')

# SELFIES -> SMILES
decoded = sf.decoder(aspirin_selfies)
print(f'解码回SMILES: {decoded}')

# SELFIES的鲁棒性:随机修改SELFIES仍能解码为合法分子
tokens = list(sf.split_selfies(aspirin_selfies))
tokens[3] = '[C]'  # 替换一个token
modified = ''.join(tokens)
decoded_modified = sf.decoder(modified)
print(f'修改后解码: {decoded_modified}')
print(f'是否合法: {Chem.MolFromSmiles(decoded_modified) is not None}')

2.2 分子图表示

将分子视为图结构:原子为节点,化学键为边。这种表示天然适合图神经网络。

import torch
from torch_geometric.data import Data

def mol_to_graph(smiles):
    """将SMILES转换为PyTorch Geometric的图数据"""
    mol = Chem.MolFromSmiles(smiles)
    if mol is None:
        return None

    # 节点特征:原子属性
    atom_features = []
    for atom in mol.GetAtoms():
        features = [
            atom.GetAtomicNum(),
            atom.GetDegree(),
            atom.GetFormalCharge(),
            int(atom.GetHybridization()),
            int(atom.GetIsAromatic()),
            atom.GetTotalNumHs(),
        ]
        atom_features.append(features)

    # 边索引和边特征
    edge_index = []
    edge_attr = []
    for bond in mol.GetBonds():
        i, j = bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()
        edge_index.extend([[i, j], [j, i]])  # 无向图
        bond_features = [
            int(bond.GetBondType()),
            int(bond.GetIsConjugated()),
            int(bond.IsInRing()),
        ]
        edge_attr.extend([bond_features, bond_features])

    x = torch.tensor(atom_features, dtype=torch.float)
    edge_index = torch.tensor(edge_index, dtype=torch.long).t().contiguous()
    edge_attr = torch.tensor(edge_attr, dtype=torch.float)

    return Data(x=x, edge_index=edge_index, edge_attr=edge_attr)

# 测试
graph = mol_to_graph('CC(=O)Oc1ccccc1C(=O)O')
print(f'节点数: {graph.num_nodes}')
print(f'边数: {graph.num_edges}')
print(f'节点特征维度: {graph.x.shape}')

2.3 3D 构象表示

分子的三维构象决定了其与蛋白质靶点的结合方式。生成 3D 构象通常使用 RDKit 的距离几何算法或 ETKDG 方法。

def generate_3d_conformer(smiles):
    """生成分子的3D构象"""
    mol = Chem.MolFromSmiles(smiles)
    mol = Chem.AddHs(mol)

    # 使用ETKDG算法生成3D构象
    AllChem.EmbedMolecule(mol, AllChem.ETKDGv3())
    AllChem.MMFFOptimizeMolecule(mol)

    # 提取原子坐标
    conf = mol.GetConformer()
    positions = []
    for i in range(mol.GetNumAtoms()):
        pos = conf.GetAtomPosition(i)
        positions.append([pos.x, pos.y, pos.z])

    return np.array(positions), mol

coords, mol_3d = generate_3d_conformer('CC(=O)Oc1ccccc1C(=O)O')
print(f'原子坐标 shape: {coords.shape}')
print(f'第一个原子坐标: {coords[0]}')

3. 分子属性预测

3.1 消息传递神经网络(MPNN)

MPNN 是图神经网络的经典范式,通过在图上迭代传递和聚合邻居信息来学习节点(原子)表示,最终通过全局池化得到分子级别的表示。

import torch
import torch.nn as nn
from torch_geometric.nn import MessagePassing, global_mean_pool
from torch_geometric.data import Batch

class MPNNLayer(MessagePassing):
    """消息传递层"""
    def __init__(self, node_dim, edge_dim, hidden_dim):
        super().__init__(aggr='add')
        self.message_fn = nn.Sequential(
            nn.Linear(node_dim * 2 + edge_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim)
        )
        self.update_fn = nn.GRU(hidden_dim, node_dim)

    def forward(self, x, edge_index, edge_attr):
        return self.propagate(edge_index, x=x, edge_attr=edge_attr)

    def message(self, x_i, x_j, edge_attr):
        return self.message_fn(torch.cat([x_i, x_j, edge_attr], dim=-1))

    def update(self, aggr_out, x):
        return self.update_fn(aggr_out.unsqueeze(0), x.unsqueeze(0)).squeeze(0)

class MolecularMPNN(nn.Module):
    """分子属性预测MPNN模型"""
    def __init__(self, node_dim=6, edge_dim=3, hidden_dim=128, n_layers=3, n_tasks=1):
        super().__init__()
        self.node_encoder = nn.Linear(node_dim, hidden_dim)
        self.layers = nn.ModuleList([
            MPNNLayer(hidden_dim, edge_dim, hidden_dim) for _ in range(n_layers)
        ])
        self.readout = nn.Sequential(
            nn.Linear(hidden_dim, 64),
            nn.ReLU(),
            nn.Linear(64, n_tasks)
        )

    def forward(self, data):
        x = self.node_encoder(data.x)
        for layer in self.layers:
            x = layer(x, data.edge_index, data.edge_attr) + x  # 残差连接
        x = global_mean_pool(x, data.batch)
        return self.readout(x)

# 训练循环示例
model = MolecularMPNN(n_tasks=1)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

# 假设已有train_loader包含分子图数据和标签
# for batch in train_loader:
#     pred = model(batch).squeeze()
#     loss = nn.MSELoss()(pred, batch.y)
#     optimizer.zero_grad()
#     loss.backward()
#     optimizer.step()

3.2 图注意力网络(GAT)

GAT 通过注意力机制为不同邻居分配不同权重,能够自适应地捕捉原子间的相互作用重要性。

from torch_geometric.nn import GATv2Conv, global_mean_pool

class MolecularGAT(nn.Module):
    def __init__(self, in_dim=6, hidden_dim=128, heads=4, n_layers=3, n_tasks=1):
        super().__init__()
        self.enc = nn.Linear(in_dim, hidden_dim)
        self.convs = nn.ModuleList()
        for _ in range(n_layers):
            self.convs.append(GATv2Conv(hidden_dim, hidden_dim // heads, heads=heads))
        self.head = nn.Linear(hidden_dim, n_tasks)

    def forward(self, data):
        x = torch.relu(self.enc(data.x))
        for conv in self.convs:
            x = torch.relu(conv(x, data.edge_index)) + x
        x = global_mean_pool(x, data.batch)
        return self.head(x)

4. 分子生成模型

4.1 分子 VAE(变分自编码器)

VAE 将分子编码到连续隐空间,然后从隐空间解码生成新分子。通过在隐空间中插值或优化,可以生成具有目标属性的分子。

class MolecularVAE(nn.Module):
    """基于SELFIES的分子VAE"""
    def __init__(self, vocab_size, max_len, embed_dim=64, latent_dim=128):
        super().__init__()
        self.max_len = max_len
        # 编码器
        self.encoder = nn.GRU(embed_dim, 256, bidirectional=True, batch_first=True)
        self.embed = nn.Embedding(vocab_size, embed_dim)
        self.fc_mu = nn.Linear(512, latent_dim)
        self.fc_logvar = nn.Linear(512, latent_dim)
        # 解码器
        self.latent_to_hidden = nn.Linear(latent_dim, 256)
        self.decoder = nn.GRU(embed_dim, 256, batch_first=True)
        self.fc_out = nn.Linear(256, vocab_size)

    def encode(self, x):
        embedded = self.embed(x)
        _, h = self.encoder(embedded)
        h = torch.cat([h[0], h[1]], dim=-1)
        return self.fc_mu(h), self.fc_logvar(h)

    def reparameterize(self, mu, logvar):
        std = torch.exp(0.5 * logvar)
        eps = torch.randn_like(std)
        return mu + eps * std

    def decode(self, z, max_len):
        h = self.latent_to_hidden(z).unsqueeze(0)
        input_token = torch.zeros(z.shape[0], 1, dtype=torch.long, device=z.device)
        outputs = []
        for _ in range(max_len):
            embedded = self.embed(input_token)
            out, h = self.decoder(embedded, h)
            logits = self.fc_out(out)
            outputs.append(logits)
            input_token = logits.argmax(dim=-1)
        return torch.cat(outputs, dim=1)

    def forward(self, x):
        mu, logvar = self.encode(x)
        z = self.reparameterize(mu, logvar)
        recon = self.decode(z, x.shape[1])
        return recon, mu, logvar

def vae_loss(recon, x, mu, logvar, beta=0.5):
    """VAE损失 = 重建损失 + β * KL散度"""
    recon_loss = nn.CrossEntropyLoss()(recon.view(-1, recon.shape[-1]), x.view(-1))
    kl_loss = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp()) / x.shape[0]
    return recon_loss + beta * kl_loss

4.2 分子扩散模型

扩散模型通过逐步去噪过程生成分子,在分子生成质量和多样性方面表现出色。

class MolecularDiffusion(nn.Module):
    """简化的分子属性条件扩散模型(在隐空间操作)"""
    def __init__(self, latent_dim=128, time_dim=64, cond_dim=32):
        super().__init()
        self.time_embed = nn.Sequential(
            nn.Linear(1, time_dim),
            nn.SiLU(),
            nn.Linear(time_dim, time_dim)
        )
        self.cond_embed = nn.Linear(cond_dim, time_dim)
        self.net = nn.Sequential(
            nn.Linear(latent_dim + time_dim * 2, 256),
            nn.SiLU(),
            nn.Linear(256, 256),
            nn.SiLU(),
            nn.Linear(256, latent_dim)
        )

    def forward(self, x, t, condition):
        t_emb = self.time_embed(t.unsqueeze(-1).float())
        c_emb = self.cond_embed(condition)
        inp = torch.cat([x, t_emb, c_emb], dim=-1)
        return self.net(inp)

def diffuse_sample(model, condition, latent_dim=128, n_steps=1000):
    """从扩散模型采样"""
    device = next(model.parameters()).device
    x = torch.randn(1, latent_dim, device=device)
    cond = torch.tensor(condition, dtype=torch.float, device=device).unsqueeze(0)

    for t in reversed(range(n_steps)):
        t_tensor = torch.tensor([t / n_steps], device=device)
        predicted_noise = model(x, t_tensor, cond)
        # 简化的去噪步骤
        alpha = 1 - t / n_steps
        x = (x - predicted_noise * (1 - alpha)) / max(alpha, 1e-8)
        if t > 0:
            x += torch.randn_like(x) * 0.01

    return x.detach()

4.3 基于强化学习的分子优化

将分子生成建模为马尔可夫决策过程,使用策略梯度方法优化生成的分子属性。

def compute_reward(smiles, target_props):
    """计算分子奖励分数"""
    mol = Chem.MolFromSmiles(smiles)
    if mol is None:
        return -1.0  # 无效分子惩罚

    reward = 0.0
    # LogP奖励(药物样分子通常在-0.4到5.6之间)
    logp = Descriptors.MolLogP(mol)
    if -0.4 <= logp <= 5.6:
        reward += 1.0
    # 分子量惩罚
    mw = Descriptors.MolWt(mol)
    if 200 <= mw <= 500:
        reward += 1.0
    # 药物相似性(Lipinski规则)
    if Descriptors.NumHDonors(mol) <= 5 and Descriptors.NumHAcceptors(mol) <= 10:
        reward += 0.5
    # 合成可及性(越低越容易合成)
    # sa_score = calculate_sa_score(mol)
    # reward += max(0, 10 - sa_score) / 10

    return reward

5. 蛋白质结构预测

5.1 AlphaFold 核心思想

AlphaFold 通过 Evoformer(处理多序列比对 MSA 和配对表示)和 Structure Module(预测原子坐标)两个核心模块,实现了接近实验精度的蛋白质结构预测。

# 使用ColabFold API进行蛋白质结构预测(简化示例)
# 实际使用需要安装 colabfold 或访问 AlphaFold DB

def fetch_predicted_structure(protein_id):
    """从AlphaFold数据库获取预测结构"""
    import urllib.request
    url = f'https://alphafold.ebi.ac.uk/files/AF-{protein_id}-F1-model_v4.pdb'
    pdb_path = f'{protein_id}.pdb'
    urllib.request.urlretrieve(url, pdb_path)
    return pdb_path

# 蛋白质序列编码(用于自定义模型)
def encode_protein_sequence(seq, max_len=1024):
    """将氨基酸序列编码为数值向量"""
    aa_vocab = 'ACDEFGHIKLMNPQRSTVWY'
    aa_to_idx = {aa: i+1 for i, aa in enumerate(aa_vocab)}
    encoded = [aa_to_idx.get(aa, 0) for aa in seq[:max_len]]
    # 填充到固定长度
    encoded += [0] * (max_len - len(encoded))
    return encoded

# 示例
seq = 'MKFLILLFNILCLFPVLAADNHGVSRELVDKGKLF'
encoded = encode_protein_sequence(seq)
print(f'序列长度: {len(seq)}, 编码后长度: {len(encoded)}')

5.2 ESMFold

ESMFold 是 Meta 开发的蛋白质结构预测模型,无需 MSA 即可预测结构,速度比 AlphaFold 快约 60 倍。

# ESMFold使用示例(需要安装esm库)
# pip install fair-esm

import esm

def predict_with_esmfold(sequence):
    """使用ESMFold预测蛋白质结构"""
    model = esm.pretrained.esmfold_v1()
    model = model.eval()

    with torch.no_grad():
        output = model.infer_pdb(sequence)

    # 保存PDB文件
    with open('predicted.pdb', 'w') as f:
        f.write(output)
    return 'predicted.pdb'

# 蛋白质语言模型嵌入(用于下游任务)
def get_protein_embedding(sequence):
    """使用ESM-2获取蛋白质序列嵌入"""
    model, alphabet = esm.pretrained.esm2_t33_650M_UR50D()
    batch_converter = alphabet.get_batch_converter()
    data = [('protein', sequence)]
    _, _, tokens = batch_converter(data)

    with torch.no_grad():
        results = model(tokens, repr_layers=[33])
    # 取最后一层的表示,平均池化
    embedding = results['representations'][33].mean(dim=1)
    return embedding

# embed = get_protein_embedding('MKFLILLFNILCLFPVLAADNHGVSRELVD')
# print(f'蛋白质嵌入维度: {embed.shape}')

6. 药物-靶点相互作用预测

预测药物分子与蛋白质靶点的结合亲和力是药物发现的核心任务。

class DTIPredictor(nn.Module):
    """药物-靶点相互作用预测模型"""
    def __init__(self, drug_dim=128, protein_dim=256, hidden_dim=128):
        super().__init__()
        # 药物编码器(MPNN)
        self.drug_encoder = nn.Sequential(
            nn.Linear(drug_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim)
        )
        # 蛋白质编码器
        self.protein_encoder = nn.Sequential(
            nn.Linear(protein_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim)
        )
        # 交互层
        self.interaction = nn.Sequential(
            nn.Linear(hidden_dim * 2, hidden_dim),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(hidden_dim, 64),
            nn.ReLU(),
            nn.Linear(64, 1),
            nn.Sigmoid()
        )

    def forward(self, drug_repr, protein_repr):
        d = self.drug_encoder(drug_repr)
        p = self.protein_encoder(protein_repr)
        combined = torch.cat([d, p], dim=-1)
        return self.interaction(combined)

# 分子指纹作为药物表示
def get_morgan_fingerprint(smiles, radius=2, n_bits=1024):
    """生成Morgan分子指纹"""
    mol = Chem.MolFromSmiles(smiles)
    fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius, nBits=n_bits)
    return np.array(fp)

# 使用示例
drug_fp = get_morgan_fingerprint('CC(=O)Oc1ccccc1C(=O)O')
print(f'分子指纹维度: {drug_fp.shape}')
print(f'非零位点数: {np.sum(drug_fp > 0)}')

7. 虚拟筛选与先导化合物优化

7.1 虚拟筛选流程

虚拟筛选从数百万化合物中快速缩小候选范围,分为基于结构的筛选(SBVS)和基于配体的筛选(LBVS)。

from rdkit.Chem import AllChem
from rdkit.DataStructs import TanimotoSimilarity

def virtual_screening(query_smiles, library_smiles, top_k=10):
    """基于配体相似性的虚拟筛选"""
    query_mol = Chem.MolFromSmiles(query_smiles)
    query_fp = AllChem.GetMorganFingerprintAsBitVect(query_mol, 2, nBits=2048)

    results = []
    for name, smi in library_smiles.items():
        mol = Chem.MolFromSmiles(smi)
        if mol is None:
            continue
        fp = AllChem.GetMorganFingerprintAsBitVect(mol, 2, nBits=2048)
        similarity = TanimotoSimilarity(query_fp, fp)
        results.append((name, smi, similarity))

    results.sort(key=lambda x: x[2], reverse=True)
    return results[:top_k]

# 示例:筛选与阿司匹林相似的化合物
library = {
    '化合物A': 'CC(=O)Oc1ccccc1C(=O)O',  # 阿司匹林本身
    '化合物B': 'CC(=O)Nc1ccc(O)cc1',      # 对乙酰氨基酚
    '化合物C': 'CC(C)Cc1ccc(cc1)C(C)C(=O)O',  # 布洛芬
    '化合物D': 'c1ccc2c(c1)cc1ccccc1c2',   # 芘
}

hits = virtual_screening('CC(=O)Oc1ccccc1C(=O)O', library)
for name, smi, sim in hits:
    print(f'{name}: Tanimoto = {sim:.3f}')

7.2 先导化合物优化

通过 AI 生成分子变体,在保持核心骨架的同时优化 ADMET(吸收、分布、代谢、排泄、毒性)属性。

def lipinski_filter(smiles):
    """Lipinski五规则过滤"""
    mol = Chem.MolFromSmiles(smiles)
    if mol is None:
        return False
    return (Descriptors.MolWt(mol) <= 500 and
            Descriptors.MolLogP(mol) <= 5 and
            Descriptors.NumHDonors(mol) <= 5 and
            Descriptors.NumHAcceptors(mol) <= 10)

def generate_analogs(base_smiles, n_samples=100):
    """基于SMILES扰动生成类似物"""
    mol = Chem.MolFromSmiles(base_smiles)
    analogs = set()

    for _ in range(n_samples * 10):
        # 随机原子替换和键修改
        new_mol = Chem.RWMol(mol)
        # 简化:对SMILES字符串做微小修改
        chars = list(base_smiles)
        idx = np.random.randint(len(chars))
        replacement = np.random.choice(['C', 'N', 'O', 'F', 'Cl'])
        chars[idx] = replacement
        new_smiles = ''.join(chars)

        new_mol_check = Chem.MolFromSmiles(new_smiles)
        if new_mol_check is not None and lipinski_filter(new_smiles):
            analogs.add(new_smiles)
        if len(analogs) >= n_samples:
            break

    return list(analogs)

# analogs = generate_analogs('CC(=O)Oc1ccccc1C(=O)O', n_samples=10)
# print(f'生成了 {len(analogs)} 个合法类似物')

8. 分子动力学模拟

分子动力学(MD)模拟追踪分子体系随时间的运动轨迹,用于评估蛋白质-配体复合物的稳定性。AI 正在加速 MD 模拟中的力场计算和采样。

# MD模拟概念性代码(使用OpenMM的简化接口)
# pip install openmm

def run_md_simulation_concept():
    """MD模拟的核心流程(概念性展示)"""
    # 1. 体系准备
    steps = {
        '加载拓扑': '从PDB文件读取原子坐标和拓扑',
        '添加溶剂': '将分子置于水盒子中',
        '添加离子': '平衡体系电荷',
        '能量最小化': '消除原子间的空间冲突',
        'NVT平衡': '恒温平衡,使体系达到目标温度',
        'NPT平衡': '恒温恒压平衡,调整体系密度',
        '生产运行': '收集轨迹数据用于分析',
    }

    # 2. AI加速的力场(概念)
    # 传统力场:基于预定义的数学函数
    # ML力场:从量子力学数据学习势能面
    # 代表模型:ANI, SchNet, MACE

    # 3. 关键分析指标
    metrics = {
        'RMSD': '均方根偏差 - 评估结构变化',
        'RMSF': '均方根波动 - 评估残基柔性',
        'Rg': '回旋半径 - 评估蛋白质紧密度',
        '氢键数': '评估结合稳定性',
        'MM/PBSA': '结合自由能计算',
    }

    return steps, metrics

steps, metrics = run_md_simulation_concept()
print('MD模拟流程:')
for k, v in steps.items():
    print(f'  {k}: {v}')

AI 加速 MD 的关键方向

  • ML 力场:用图神经网络替代传统力场,精度接近量子力学,速度接近经典力场
  • 增强采样:用 AI 学习集体变量,加速稀有事件的采样
  • 粗粒化模型:通过深度学习将全原子模型映射为粗粒化表示

9. 临床试验 AI 辅助

AI 在临床试验阶段的应用日益重要:

def clinical_trial_ai_applications():
    """AI在临床试验中的应用场景"""
    applications = {
        '患者招募': {
            '技术': 'NLP分析电子病历,匹配入排标准',
            '效果': '招募速度提升30-50%',
        },
        '剂量优化': {
            '技术': '贝叶斯自适应设计,PK/PD建模',
            '效果': '减少所需受试者数量,缩短试验周期',
        },
        '终点预测': {
            '技术': '生存分析模型,预测治疗反应',
            '效果': '更准确的样本量估计',
        },
        '安全性监测': {
            '技术': '实时不良事件信号检测',
            '效果': '早期发现安全性问题',
        },
        '数字孪生': {
            '技术': '基于历史数据生成虚拟对照组',
            '效果': '减少安慰剂组人数',
        },
    }
    return applications

# 患者分层示例
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score

def patient_stratification(features, labels):
    """基于生物标志物的患者分层"""
    clf = RandomForestClassifier(n_estimators=100, random_state=42)
    scores = cross_val_score(clf, features, labels, cv=5, scoring='roc_auc')
    print(f'患者分层 AUC: {scores.mean():.3f} ± {scores.std():.3f}')
    return clf

10. 实战案例:分子性质预测与生成

以下构建一个完整的分子性质预测与生成流水线。

import torch
import torch.nn as nn
import numpy as np
from rdkit import Chem
from rdkit.Chem import AllChem, Descriptors
from torch_geometric.data import Data, DataLoader
from torch_geometric.nn import GCNConv, global_mean_pool

# === 第一步:数据准备 ===
def prepare_molecular_dataset(smiles_list, property_fn):
    """准备分子图数据集"""
    dataset = []
    for smi in smiles_list:
        mol = Chem.MolFromSmiles(smi)
        if mol is None:
            continue
        graph = mol_to_graph(smi)  # 使用前面定义的函数
        if graph is None:
            continue
        graph.y = torch.tensor([property_fn(mol)], dtype=torch.float)
        dataset.append(graph)
    return dataset

# 生成示例数据
np.random.seed(42)
sample_smiles = [
    'CC(=O)Oc1ccccc1C(=O)O', 'CC(=O)Nc1ccc(O)cc1',
    'CC(C)Cc1ccc(cc1)C(C)C(=O)O', 'Cn1c(=O)c2c(ncn2C)n(C)c1=O',
    'c1ccc(-c2ccccc2)cc1', 'CC(=O)c1ccc(cc1)C(=O)C',
    'O=c1ccc2ccccc2o1', 'c1ccc2c(c1)[nH]c1ccccc12',
    'CC12CCC3C(C1CCC2O)CCC4=CC(=O)CCC34C', 'CC(=O)OC1CC2CCC3C(CCC4(C)C3CCC4(O)C(=O)CO)C2(C)CC1O',
]

dataset = prepare_molecular_dataset(sample_smiles, Descriptors.MolLogP)
print(f'数据集大小: {len(dataset)}')
print(f'样本: {dataset[0]}')

# === 第二步:模型定义 ===
class MolecularGNN(nn.Module):
    def __init__(self, in_dim, hidden_dim=64, out_dim=1):
        super().__init__()
        self.conv1 = GCNConv(in_dim, hidden_dim)
        self.conv2 = GCNConv(hidden_dim, hidden_dim)
        self.conv3 = GCNConv(hidden_dim, hidden_dim)
        self.fc = nn.Linear(hidden_dim, out_dim)
        self.dropout = nn.Dropout(0.2)

    def forward(self, data):
        x, edge_index, batch = data.x, data.edge_index, data.batch
        x = torch.relu(self.conv1(x, edge_index))
        x = self.dropout(x)
        x = torch.relu(self.conv2(x, edge_index))
        x = self.dropout(x)
        x = torch.relu(self.conv3(x, edge_index))
        x = global_mean_pool(x, batch)
        return self.fc(x)

# === 第三步:训练 ===
def train_model(dataset, epochs=100):
    train_size = int(0.8 * len(dataset))
    train_data = dataset[:train_size]
    test_data = dataset[train_size:]

    train_loader = DataLoader(train_data, batch_size=16, shuffle=True)
    test_loader = DataLoader(test_data, batch_size=16)

    model = MolecularGNN(in_dim=dataset[0].x.shape[1])
    optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
    criterion = nn.MSELoss()

    for epoch in range(epochs):
        model.train()
        total_loss = 0
        for batch in train_loader:
            pred = model(batch).squeeze()
            loss = criterion(pred, batch.y)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            total_loss += loss.item()

        if (epoch + 1) % 20 == 0:
            model.eval()
            test_loss = 0
            with torch.no_grad():
                for batch in test_loader:
                    pred = model(batch).squeeze()
                    test_loss += criterion(pred, batch.y).item()
            print(f'Epoch {epoch+1}, Train Loss: {total_loss/len(train_loader):.4f}, '
                  f'Test Loss: {test_loss/len(test_loader):.4f}')

    return model

# model = train_model(dataset, epochs=100)

# === 第四步:分子生成 ===
def generate_novel_molecules(base_smiles, model, n_generate=10):
    """基于梯度引导的分子生成"""
    generated = []
    base_mol = Chem.MolFromSmiles(base_smiles)
    # 简化:通过随机SMILES变异生成候选
    chars = list('CNOSF()=c123456789')

    attempts = 0
    while len(generated) < n_generate and attempts < 1000:
        attempts += 1
        smiles_chars = list(Chem.MolToSmiles(base_mol))
        # 随机修改1-2个字符
        n_changes = np.random.randint(1, 3)
        for _ in range(n_changes):
            idx = np.random.randint(len(smiles_chars))
            smiles_chars[idx] = np.random.choice(chars)
        new_smiles = ''.join(smiles_chars)
        new_mol = Chem.MolFromSmiles(new_smiles)
        if new_mol is not None:
            generated.append((new_smiles, Descriptors.MolLogP(new_mol)))

    return generated

# novel = generate_novel_molecules('CC(=O)Oc1ccccc1C(=O)O', model)
# for smi, logp in novel[:5]:
#     print(f'{smi} -> LogP: {logp:.2f}')

11. 挑战与前沿方向

当前挑战

  1. 数据稀缺性:高质量标注的药物活性数据极为有限,尤其针对新靶点
  2. 化学空间巨大:类药物分子的化学空间约有 10^60 种可能,穷举搜索不可行
  3. 多目标优化:需要同时优化活性、选择性、ADMET 属性、合成可行性等多个目标
  4. 实验验证鸿沟:计算预测与湿实验结果之间仍存在显著差距
  5. 可解释性:黑盒模型的预测缺乏化学直觉支撑,难以获得药物化学家信任

前沿方向

  • 基础模型:如 MolFormer、Uni-Mol 等在大规模分子数据上预训练的通用模型
  • 多模态学习:融合分子图、SMILES、3D 构象、蛋白质序列等多种模态
  • 因果推理:从相关性建模转向因果机制理解,提升模型泛化能力
  • 主动学习:智能选择最有信息量的实验进行验证,最大化实验效率
  • 量子机器学习:利用量子计算加速分子模拟和优化
  • 闭环实验:AI 设计 → 机器人合成 → 自动检测 → 反馈优化的全自动流程

AI 药物发现正处于从概念验证到产业化落地的关键转折期。掌握分子表示学习、属性预测和生成模型三大核心技术,理解从计算到实验的完整闭环,是进入这一领域的基础。随着基础模型和自动化实验平台的发展,AI 辅助药物发现的效率还将持续提升。

内容声明

本文内容为AI技术学习教程,仅供学习参考。如涉及技术问题,欢迎通过 xurj005@163.com 与我们交流。

目录