第3章 交叉熵损失:原理与在分子生成中的应用
说实话,在分子生成这个领域摸爬滚打这些年,交叉熵损失是我打交道最多的损失函数之一。它看起来简单——就一个公式,几行代码完事。但你真要在分子生成任务里把它用好,里面门道不少。我来拆解一下。
3.1 交叉熵损失的核心思想
交叉熵损失最早来自信息论,用来衡量两个概率分布之间的“距离”。我个人的理解更直白一些:假如模型预测的原子分布是 [0.3, 0.7],真实分子是 [0, 1],那交叉熵就算这两者之间的差距。
数学上长这样:
H(p, q) = -Σ p(x) · log q(x)
其中 p 是真实分布(通常是 one-hot),q 是模型预测。放到分子生成场景里,就是每个位置上,模型猜对了哪个原子或化学键,损失值就小;猜错了,损失值就大。
关键点:交叉熵是“惩罚错误”型的损失函数。你猜得越离谱,它惩罚得越狠。这对分子生成来说是好事——一个错误的原子排布可能导致分子完全失去药效。
3.2 分子生成里怎么用交叉熵
分子生成主要有两种范式,交叉熵的应用方式也不一样。
3.2.1 自回归分子生成
这种模式很常见,类似语言模型逐字生成句子。模型每次预测下一个原子的类型、位置、化学键等。
损失计算过程:
- 模型输入前面已生成的分子片段
- 对每个位置输出一个概率分布(如原子类型 softmax)
- 用交叉熵对比真实的下一个原子是什么
- 所有位置的损失加起来,反向传播
代码实现很简单:
import torch.nn as nn
criterion = nn.CrossEntropyLoss()
# 假设 batch_size=32, seq_len=50, vocab_size=28
logits = model(molecule_input) # 形状: [32, 50, 28]
targets = molecule_targets # 形状: [32, 50]
loss = criterion(logits.reshape(-1, 28), targets.reshape(-1))
注意这个 reshape,当时我踩过坑——CrossEntropyLoss 期望输入是 [N, C] 和 [N],必须把序列维度展平。
3.2.2 图神经网络分子生成
图生成里,模型预测的是边是否存在、边类型、节点属性。损失函数通常分三部分:
# 图生成损失拆解
loss_node = cross_entropy(node_logits, node_labels) # 节点属性损失
loss_edge = cross_entropy(edge_logits, edge_labels) # 边存在性损失
loss_type = cross_entropy(type_logits, type_labels) # 边类型损失
total_loss = loss_node + loss_edge + loss_type
经验之谈:我习惯给三部分损失加不同的权重。比如边存在性损失更重要些,权重可以设到 2.0。分子骨架的结构信息,大部分藏在边的连接关系里。
3.3 交叉熵在分子生成里常见的坑
聊几个我实际遇到过的典型问题。
3.3.1 类别不平衡
分子数据里,某些原子类型出现频率极高,比如碳氢氧;而稀有原子如碘、氟,频率就很低。普通交叉熵对所有类别一视同仁,导致模型干脆忽略稀有原子。
解决方案:
# 计算各类别权重
class_weights = compute_inverse_frequency(vocab)
criterion = nn.CrossEntropyLoss(weight=class_weights)
# 也可以做标签平滑
criterion_smooth = nn.CrossEntropyLoss(label_smoothing=0.1)
标签平滑挺管用的——它不让模型对预测分布过于自信,在分子生成里能增加生成多样性。
3.3.2 生成序列长度偏差
分子长度差异大,短的分子只有几个原子,长的可能上百。模型容易偏好长度居中的分子,短的生成不了,长的也生成不了。
我当时做过一次实验:
# 长度归一化的交叉熵
loss_per_token = cross_entropy(logits, targets) # 形状: [batch, seq_len]
# 对每个序列求平均损失
loss_per_sequence = loss_per_token.sum(dim=1) / valid_token_counts
# 这样长短分子在损失贡献上更公平
注意:长度归一化要小心。如果某个分子特别短(比如只有 3 个原子),均值会很不稳定。我设了一个最低长度阈值,低于阈值的分子用原始损失。
3.4 交叉熵 vs 其他损失函数:选哪个?
| 损失函数 | 适用场景 | 我的使用经验 |
|---|---|---|
| 交叉熵 | 原子类型预测、键类型预测、序列式生成 | 最稳定,80% 的情况用它就够了 |
| 负对数似然 | 自回归模型、VAE 解码 | 和交叉熵本质一样,只是形式不同 |
| KL 散度 | VAE 的潜空间正则化 | 常和交叉熵组合使用,做 VAE 损失 |
| 均方误差 | 属性回归(如预测 LogP) | 分子属性预测用,生成任务少用 |
说白了,我在大多数分子生成项目里,主力损失函数就是交叉熵。偶尔和 KL 散度搭伙用,比如做变分分子自编码器的时候。
3.5 交叉熵损失在分子生成中的评估
光看损失值降下来了不够。我记得有次损失曲线完美下降,生成的分子却全是乱码。后来才知道,模型学会了“预测‘停止符’就有低损失”的投机行为。
我建议额外跟踪几个指标:
- 有效分子比例:生成的分子能否通过化学规则校验
- 原子类型准确率:每个位置预测的原子类型正确率
- 键连接准确率:化学键预测的精确率
- 唯一性:生成的分子是否多样化
交叉熵损失降得好,不一定意味着这些指标就好。我见过太多损失很低但生成质量很差的模型了。
3.6 实操建议
如果你刚起步用交叉熵做分子生成,我的建议很简单:
- 先从标准交叉熵开始,不加任何花哨的改进。跑通流程再说优化的事。
- 运行模型后,先看生成的分子是否合法,而不是只盯着损失曲线。
- 如果发现生成效果不理想,优先检查数据预处理。我曾经 DEBUG 了一个月,最后发现是 SMILES 解析出了问题,交叉熵学的全是噪声。
- 调整权重时不要过猛,比如把稀有原子权重设到 100 倍,模型会学歪。
交叉熵在分子生成里确实是基石级别的损失函数。别因为它常见就低估它——用好它,够你解决绝大部分分子生成的训练问题了。