字节面试手撕题:神经网络模型损失函数与梯度推导
题目
设神经网络模型,其中
是Sigmoid函数:
(1)写出Sigmoid函数的表达式并求导,对于简单的二分类问题,损失函数采用什么?
(2)采用(1)中的损失函数,推导反向传播中损失函数对神经网络参数的梯度表达式;
(3)对于单个样本,写出神经网络参数更新的表达式;对于多个样本(如batch_size=10)呢?
(4)若训练中发现模型损失为NaN,问题可能出在哪里,有什么排查方法?
解答
(1)Sigmoid函数是一种常用的激活函数,用于将输入值映射到(0,1)区间,其表达式为:
Sigmoid函数的导数可以通过链式法则求得。令 ,则导数如下:
推导过程:
设 ,令
,则
,又
因此,
注意到 ,因为
对于简单的二分类问题,损失函数采用二分类交叉熵损失(Binary Cross-Entropy Loss)。对于单个样本,损失函数 L 定义为:
其中:
t 是真实标签(取值为 0 或 1),
y 是模型的预测概率(本题中即 )。
该损失函数衡量了预测概率 y 与真实标签 t 之间的差异,常用于逻辑回归和神经网络中的二分类任务。
(2)对于给定神经网络模型 ,其中
是 Sigmoid 函数,损失函数采用二分类交叉熵损失(对于单个样本):
其中 t 是真实标签(0 或 1), y 是预测值。
通过反向传播,损失函数对参数 的梯度表达式如下:
损失函数对 的梯度:
损失函数对 的梯度:
推导说明:
推导过程使用链式法则:
1. 计算 :
2. 计算 (其中
):利用 Sigmoid 函数的导数:
3. 计算 :
4. 最后,计算对 的梯度:
,所以
,所以
这些梯度表达式可用于梯度下降算法中更新参数 。对于多个样本的情况,通常取所有样本梯度的平均值或求和。
(3)参数更新表达式
对于单个样本:
假设输入为 x,真实标签为 t,预测值为 。损失函数对参数的梯度为:
参数更新表达式为:
对于多个样本(batch_size=10):
假设有10个样本,输入为 ,真实标签为
,预测值为
。损失函数对参数的梯度为平均梯度:
参数更新表达式为:
在实际实现中,通常使用批量梯度下降,即计算整个批次的梯度后更新参数。学习率 需要根据具体问题调整。
(4)模型训练中出现损失为NaN的原因及排查方法
可能的原因:
1. 学习率过高:过高的学习率可能导致参数更新步长过大,使得模型参数变得非常大,甚至溢出。
2. 梯度爆炸:梯度变得非常大,导致参数更新后出现非常大的数值,进而使后续计算出现NaN。
3. 数据问题:数据中存在缺失值(NaN)或无穷值(Inf),或者数据未经预处理(如归一化/标准化)导致数值范围过大。
4. 损失函数定义问题:例如,在计算交叉熵损失时,对预测值取对数,如果预测值为0或负数(注意:sigmoid输出在0到1之间,但若输入极大或极小,sigmoid输出可能非常接近0或1,取对数时可能得到负无穷,但通常不会出现负数,除非激活函数使用不当)但若使用其他激活函数可能导致输出为负。
5. 除零操作:在损失函数或梯度计算中可能存在除以零的风险。
6. 数值不稳定:例如,在softmax函数中,如果输入值非常大,可能导致指数运算溢出。
排查方法:
1. 降低学习率:尝试将学习率减小一个数量级,观察是否还会出现NaN。
2. 梯度裁剪:在反向传播过程中,对梯度进行裁剪,限制梯度的最大值,防止梯度爆炸。
3. 检查数据:确认输入数据中是否包含NaN或Inf。可以使用numpy或pandas检查数据。
4. 数据预处理:对输入数据进行归一化或标准化,使其数值范围在一个合理的区间(例如,0均值1方差,或者缩放到[0,1]区间)。
5. 检查损失函数:确保在计算损失函数时,对数值稳定性做了处理。例如,在计算交叉熵损失时,可以对预测值进行裁剪,避免取对数时出现无穷大(如将预测值限制在[epsilon, 1-epsilon]之间,其中epsilon是一个很小的正数)。
6. 使用调试工具:在训练过程中打印出损失、梯度、参数等中间变量的值,定位NaN第一次出现的位置。例如,可以在每个epoch后打印损失,或者使用调试器逐步执行。
7. 初始化参数:检查模型参数的初始化是否合适。不合适的初始化(如初始值过大)可能导致输出非常大。
8. 使用正则化:添加正则化项(如L2正则化)可能有助于防止参数变得过大。
针对二分类模型的特定情况,由于模型简单,可能的原因主要是学习率过高、数据问题或损失计算中的对数问题。可以检查学习率,确保输入数据没有异常,并在计算交叉熵损失时对预测值进行裁剪。