You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

纯NumPy实现Softmax+CrossEntropy神经网络故障排查求助

我帮你排查了代码里的几个核心问题,这些应该是导致模型始终预测单一类别的关键原因:

1. 最致命错误:交叉熵+Softmax的梯度计算逻辑错误

当交叉熵损失和Softmax激活函数搭配使用时,不需要单独计算两个函数的导数再相乘——两者结合后的梯度有非常简洁的简化形式,这是多分类任务中最常用的优化技巧:

  • 交叉熵损失公式:$L = -\sum(y \cdot \log(pred))$,其中$pred$是Softmax的输出
  • 对Softmax输入$z$(即self.z2)求导,最终结果为:$\frac{dL}{dz} = pred - y$

你之前分开计算cross_entropy_derivative和softmax_derivative的方式完全错误,尤其是softmax_derivative的实现(Softmax的导数是雅可比矩阵,并非逐元素运算),直接导致梯度更新完全偏离正确方向。

2. 损失函数计算未取均值

你的cross_entropy函数返回的是每个样本的损失数组,没有做均值/求和处理,这会导致损失值量级过大,梯度更新不稳定。

3. 学习率过小

0.001的学习率对于简单的IRIS数据集来说太小,10000次迭代可能不足以让模型收敛到有效参数。

4. 权重初始化不合理

使用np.random.rand生成[0,1]区间的初始权重,容易导致Sigmoid激活函数直接进入饱和区,引发梯度消失,模型无法更新参数。


修改后的关键代码片段

1. 修正神经网络类的初始化、前馈和反向传播

class NeuralNetwork:
    def __init__(self, x, y):
        self.x = x
        input_dim = self.x.shape[1]
        hidden_dim = 16
        output_dim = y.shape[1]
        # 使用Xavier初始化避免Sigmoid饱和
        self.weights1 = np.random.randn(input_dim, hidden_dim) * np.sqrt(1/(input_dim + hidden_dim))
        self.bias1 = np.zeros(hidden_dim)  # 偏置初始化为0更稳妥
        self.weights2 = np.random.randn(hidden_dim, output_dim) * np.sqrt(1/(hidden_dim + output_dim))
        self.bias2 = np.zeros(output_dim)
        self.y = y
        self.pred = np.zeros(y.shape)
        self.lr = 0.01  # 调大学习率加快收敛

    def feedforward(self):
        self.layer1 = sigmoid(np.dot(self.x, self.weights1) + self.bias1)
        self.z2 = np.dot(self.layer1, self.weights2) + self.bias2  # 保存Softmax的输入,用于梯度计算
        self.layer2 = softmax(self.z2)
        return self.layer2

    def backprop(self):
        batch_size = self.x.shape[0]
        # 交叉熵+Softmax的简化梯度:pred - y
        dz2 = self.pred - self.y
        
        # 计算输出层权重和偏置的梯度(除以batch_size做均值,稳定训练)
        d_weights2 = np.dot(self.layer1.T, dz2) / batch_size
        d_bias2 = np.sum(dz2, axis=0) / batch_size
        
        # 计算隐藏层梯度
        dz1 = np.dot(dz2, self.weights2.T) * sigmoid_derivative(self.layer1)
        d_weights1 = np.dot(self.x.T, dz1) / batch_size
        d_bias1 = np.sum(dz1, axis=0) / batch_size
        
        # 更新参数(注意是减号:梯度下降)
        self.weights1 -= self.lr * d_weights1
        self.weights2 -= self.lr * d_weights2
        self.bias1 -= self.lr * d_bias1
        self.bias2 -= self.lr * d_bias2

    # 其余train/predict/evaluate方法保留,仅修改evaluate里的损失计算
    def evaluate(self, y, pred):
        self.y = y
        self.pred = pred
        self.loss = cross_entropy(self.pred, self.y)
        return self.loss

2. 修正交叉熵损失函数

def cross_entropy(X,y):
    X = X.clip(min=1e-8, max=None)
    # 对所有样本的损失取均值
    return np.mean(np.where(y==1, -np.log(X), 0).sum(axis=1))

3. 删除无用的导数函数

你可以直接删除cross_entropy_derivative和softmax_derivative,因为现在已经不需要它们了。


验证修改效果

修改后重新运行训练代码,你会看到损失值持续下降,最终模型能准确区分IRIS的三个类别。核心逻辑是:

  • 用简化后的梯度公式保证参数更新方向正确
  • 合理的初始化和学习率让模型快速收敛
  • 标准化的损失计算让训练过程更稳定

内容的提问来源于stack exchange,提问作者DrSpill

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 18:22:38