交叉熵损失
含义:预测 logits 与真实类别标签之间的差异,是分类任务的标准损失函数。
交叉熵计算公式

python
# logits --- 原始分数 (B, C),C 为类别数,targets --- 真实类别索引 (B,)
def cross_entropy_loss(logits, targets):
log_probs = logits - torch.logsumexp(logits, dim=-1, keepdim=True)
return -log_probs[torch.arange(targets.shape[0]), targets].mean()
注意:
- log_probs = logits - torch.logsumexp(logits, dim=-1, keepdim=True)完成的是log-softmax操作
- log_probs[torch.arange(targets.shape[0]), targets]取出对应目标类别的概率
logsumexp采用了"平移技巧",避免了直接计算大数的指数,保证了计算过程的数值稳定性。
KL散度
交叉熵损失和KL散度的区别 
当KL散度的P分布是固定的时候,则熵值为0,转换为交叉熵损失
标签平滑
含义:通过将 one-hot 目标分布与均匀分布混合来防止过度自信,在 Transformer 训练中被广泛使用。
python
def label_smoothing(logits, targets, smoothing=0.1):
N, C = logits.shape
logits_max = logits.max(dim=-1, keepdim=True).values
shifted = logits - logits_max
log_probs = shifted - torch.log(torch.exp(shifted).sum(dim=-1, keepdim=True))
soft_targets = torch.full_like(log_probs, smoothing / (C - 1))
# 在维度为1进行写入,targets.unsqueeze(1)提供坐标,1.0 - smoothing为具体的值
soft_targets.scatter_(1, targets.unsqueeze(1), 1.0 - smoothing)
return -(soft_targets * log_probs).sum(dim=-1).mean()
注意:
- 防止过拟合:模型不会对正确类别过于自信(输出概率接近 1),从而提升泛化能力
- 缓解标签噪声:即使数据中有少量错误标注,平滑标签也能降低其负面影响。
Kaiming 初始化
含义:根据 fan-in 设置权重方差,以在 ReLU 网络中保持信号幅度,防止激活值消失或爆炸。
python
def kaiming_init(weight):
fan_in = weight.shape[1] if weight.dim() >= 2 else weight.shape[0]
std = math.sqrt(2.0 / fan_in)
with torch.no_grad():
weight.normal_(0, std)
return weight
注意:
- 方差除以fan_in的原因是,两个特征相乘会导致方差由1变为fan_in
- Xavier 初始化到这就结束了,但是Kaiming 初始化考虑了ReLU将负值置零所造成的方差减半效应,所以方差又乘以2了。
Adam优化器
含义:结合了动量(一阶矩)和 RMSProp(二阶矩),并通过偏差校正实现自适应的逐参数学习率。
python
class MyAdam:
def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8):
self.params = list(params)
self.lr = lr
self.beta1, self.beta2 = betas
self.eps = eps
self.t = 0
self.m = [torch.zeros_like(p) for p in self.params]
self.v = [torch.zeros_like(p) for p in self.params]
def step(self):
self.t += 1
with torch.no_grad():
for i, p in enumerate(self.params):
if p.grad is None:
continue
self.m[i] = self.beta1 * self.m[i] + (1 - self.beta1) * p.grad
self.v[i] = self.beta2 * self.v[i] + (1 - self.beta2) * p.grad ** 2
m_hat = self.m[i] / (1 - self.beta1 ** self.t)
v_hat = self.v[i] / (1 - self.beta2 ** self.t)
p -= self.lr * m_hat / (torch.sqrt(v_hat) + self.eps)
def zero_grad(self):
for p in self.params:
if p.grad is not None:
p.grad.zero_()
注意:
- 利用一阶矩动量来利用惯性来计算梯度更新的方向
- 利用二阶矩来计算梯度的变化幅度,自适应步长:幅度大时学习率减小,幅度小时学习率增大
- 鲁棒性很好
- 内存占用率高
余弦学习率调度(含预热)
含义:在预热阶段线性增加学习率,之后按余弦曲线衰减,广泛用于 Transformer 训练。
python
def cosine_lr_schedule(step, total_steps, warmup_steps, max_lr, min_lr=0.0):
if step < warmup_steps:
return max_lr * step / warmup_steps
if step >= total_steps:
return min_lr
progress = (step - warmup_steps) / (total_steps - warmup_steps)
return min_lr + 0.5 * (max_lr - min_lr) * (1.0 + math.cos(math.pi * progress))
- 预热阶段防止训练初期因权重随机初始化导致梯度不稳定,让小学习率"热身"。
- 预热结束后,学习率按照余弦函数的形状从 max_lr 平滑下降到 min_lr,有助于模型在训练后期更精细地收敛到最优解。
梯度范数裁剪
含义:在所有参数梯度的 L2 范数超过阈值时进行缩放,防止梯度爆炸。
python
def clip_grad_norm(parameters, max_norm):
parameters = [p for p in parameters if p.grad is not None]
total_norm = torch.sqrt(sum(p.grad.norm() ** 2 for p in parameters))
clip_coef = max_norm / (total_norm + 1e-6)
if clip_coef < 1:
for p in parameters:
p.grad.mul_(clip_coef)
return total_norm.item()
梯度累积
含义:通过在多个小批次上累积梯度后执行一次优化器步骤,模拟大批次训练。
python
def accumulated_step(model, optimizer, loss_fn, micro_batches):
optimizer.zero_grad()
total_loss = 0.0
n = len(micro_batches)
for x, y in micro_batches:
loss = loss_fn(model(x), y) / n
loss.backward()
total_loss += loss.item()
optimizer.step()
return total_loss
- x,y不是单指一个样本,而是一个小batch的样本
- loss_fn返回的是小batch样本的平均损失
- 更稳定的梯度方向:大批次计算的梯度是更多样本的平均值,噪声更小,方向更准确,有助于模型收敛到更优的解。
混合精度训练步骤
含义:用 fp16 做前向/反向传播(更快、更省内存),同时保留 fp32 主权重以保证数值稳定性。Loss scaling 防止 fp16 梯度下溢。
步骤:
- 将模型转为 fp16,运行前向传播
- 计算 loss,乘以 loss_scale
- 对缩放后的 loss 做反向传播
- 反缩放梯度(除以 loss_scale)
- 更新优化器(fp32 主权重)
- 将模型恢复为 fp32
- 返回原始(未缩放)loss
python
def mixed_precision_step(model, optimizer, loss_fn, x, y, loss_scale=1024.0):
# 1. Cast to fp16 for forward pass
model.half()
x_fp16 = x.half()
with torch.no_grad():
pass # weights already fp16
output = model(x_fp16)
loss = loss_fn(output.float(), y)
loss_val = loss.item()
# 2. Scale loss and backward
optimizer.zero_grad()
(loss * loss_scale).backward()
# 3. Unscale gradients
for p in model.parameters():
if p.grad is not None:
p.grad.data = p.grad.data.float() / loss_scale
# 4. Update (fp32 master weights via optimizer)
model.float()
optimizer.step()
return loss_val
- 加速训练速度
- 大幅降低显存占用
- 保持数值稳定性