第4章 深度学习基础:神经网络核心要素
深度学习在QSAR里到底怎么用?说白了,就是搭一个多层神经网络,让它在分子结构和活性之间找到映射关系。这一章我就把几个最核心的组件掰开揉碎讲清楚——网络结构长什么样、激活函数怎么选、损失函数干什么用、反向传播怎么算梯度、优化器怎么更新参数。这些东西你搞明白了,后面搭多任务模型才不会懵。
4.1 神经网络结构——搭积木的基本单元
神经网络本质上就是个多层函数嵌套。每一层把输入做一次线性变换,再套个非线性激活,然后传给下一层。最基本的单元就是神经元,也叫节点。
一个典型的全连接层长这样:
输出 = 激活函数( 权重 × 输入 + 偏置 )
我刚开始做QSAR项目时,以为层数越多效果一定越好,结果在hERG毒性预测任务上训了一个8层网络,过拟合得一塌糊涂。后来才明白,对于几千个分子样本,3~4层往往就够了。
4.2 激活函数——给网络注入非线性
如果没有激活函数,多层线性变换等价于一层,你再怎么堆层都白搭。激活函数就是用来打破线性限制的。
| 激活函数 | 公式 | 适用场景 | 我踩过的坑 |
|---|---|---|---|
| Sigmoid | 1 / (1 + e-x) | 二分类输出层 | 隐藏层用Sigmoid,梯度在两端几乎为0,深层根本训不动 |
| Tanh | (ex - e-x) / (ex + e-x) | 隐藏层(零中心) | 比Sigmoid好一点,但仍有梯度饱和问题 |
| ReLU | max(0, x) | 隐藏层(默认首选) | 学习率太大时神经元会死掉,输出始终是0 |
| Leaky ReLU | max(αx, x), α=0.01 | 隐藏层(防神经元死亡) | α太小跟ReLU没区别,太大又丢失非线性 |
| Softmax | ex_i / Σex_j | 多分类输出层 | 数值不稳定时加个logits稳定版实现 |
我个人习惯,隐藏层无脑先用ReLU,如果出现大量死亡神经元,换成Leaky ReLU或ELU。输出层则根据任务选——回归用线性,二分类用Sigmoid,多分类用Softmax。
4.3 损失函数——告诉模型错在哪
损失函数度量模型预测和真实标签之间的差距。你想想看,模型靠这个信号来调整自己,所以选错损失函数,方向就错了。
QSAR里最常见的场景:
- 回归任务(预测pIC50、logP等连续值):均方误差(MSE)或平均绝对误差(MAE)。
- 二分类任务(活性/非活性、毒性/无毒):二分类交叉熵(Binary Cross-Entropy)。
- 多分类任务(激酶抑制类型等):多分类交叉熵(Categorical Cross-Entropy)。
- 多任务场景:各任务损失加权求和,权重可以自适应学习。
# PyTorch中常见的损失函数
import torch.nn as nn
# 回归
mse_loss = nn.MSELoss() # 均方误差
mae_loss = nn.L1Loss() # 平均绝对误差
# 分类
bce_loss = nn.BCEWithLogitsLoss() # 二分类(自带sigmoid)
ce_loss = nn.CrossEntropyLoss() # 多分类(自带softmax)
我在做多任务学习时,经常遇到不同任务损失尺度差几个数量级。比如一个任务是回归pIC50(MSE大约在0.5~2.0),另一个是二分类毒性(交叉熵大约在0.1~0.5)。如果不做加权平衡,大尺度任务会主导梯度。
4.4 反向传播与梯度计算——神经网络的学习引擎
反向传播就是用链式法则从输出层往输入层逐层求梯度。你可以理解为:误差从输出端一路往回传,每经过一层就把梯度分配给该层的参数。
为什么会这样?因为前向传播是复合函数,求导就得用链式法则一层层拆。有了梯度,参数才能朝减小损失的方向更新。
我曾经在训一个7层的分子性质预测网络时,发现前几层的梯度几乎是0,验证集loss死活不降。折腾了两天才意识到是梯度消失了——隐藏层用了Sigmoid,叠加后梯度在0附近,根本传不回去。
# PyTorch中查看每层梯度的示例
for name, param in model.named_parameters():
if param.grad is not None:
print(f"{name}: grad_mean={param.grad.mean().item():.6f}, "
f"grad_std={param.grad.std().item():.6f}")
4.5 优化器——控制参数更新的方向盘
有了梯度,怎么用它来更新参数?这就是优化器的事。市面上常见的优化器各有脾气:
| 优化器 | 核心思路 | 优点 | 缺点 |
|---|---|---|---|
| SGD | 梯度直接乘学习率 | 简单,泛化性好 | 收敛慢,需要精细调学习率 |
| SGD + Momentum | 累积历史梯度方向 | 加速收敛,越过局部极小 | 多了一个动量超参需要调 |
| Adam | 自适应学习率 + 动量 | 收敛快,超参鲁棒 | 有时泛化不如SGD |
| AdamW | Adam + 解耦权重衰减 | 比Adam正则化更好 | 相对较新,部分场景未普及 |
我个人习惯:先用Adam跑50~100个epoch,快速摸到一个不错的区域,然后换SGD + Momentum做微调,提升泛化能力。这个组合在好几个QSAR竞赛里帮我拿到了前排名次。
4.6 核心组件协作流程
下面这张图把五个核心组件串起来了。你顺着箭头看一遍,就是一次完整的训练迭代:
看明白了吧?数据从输入层进入网络,经过每一层的线性变换和非线性激活,到达输出层得到预测值;然后损失函数计算预测值和真实值的差距;反向传播把这个差距的梯度逐层往回传;优化器拿着梯度更新所有参数。一次完整的迭代就这样完成了。
4.7 选型组合与实用建议
讲了这么多,你在QSAR项目里到底怎么配?我直接给一套经过实战检验的默认配置:
- 网络结构:3层全连接,每层128个神经元,层间加BatchNorm和Dropout(p=0.2~0.3)。
- 激活函数:隐藏层用ReLU,输出层根据任务定(回归→线性,二分类→Sigmoid)。
- 损失函数:单任务按任务类型选;多任务用加权和 + 不确定性加权。
- 优化器:先用Adam(lr=1e-3)跑热身,再切SGD+Momentum(lr=1e-2, momentum=0.9)精调。
- 学习率调度:CosineAnnealing或ReduceLROnPlateau。
- 早停:验证集loss连续15个epoch不降就停。
这一章的内容是后面所有多任务模型的基石。你把这些组件吃透了,后面理解多任务学习中的梯度冲突、任务权重平衡、共享底层结构这些东西,就会轻松很多。