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

SGD感知器(Python)无法训练MNIST数字识别问题排查

基于SGD的感知器MNIST数字识别问题分析与解答

代码中的核心错误(导致无法学习的原因)

  • 样本维度处理错误:原代码中sample = np.append(X_train[s], [])未将28×28的图像展平为784维向量,导致输入向量维度与权重矩阵不匹配,加权和计算逻辑完全错误。正确做法是使用sample = X_train[s].flatten()。
  • 梯度计算逻辑错误:δ_j是每个输出神经元的独立误差项,公式应为δ_j = -(d_j - y_j) * f'(net_j)(元素级乘法,而非内积)。原代码将误差向量与导数向量做内积得到单一标量,再用该标量统一更新所有神经元的权重,完全丢失了每个输出神经元的独立梯度信息,导致权重更新方向错误。
  • 训练终止条件不合理:仅以单个样本的误差小于阈值就终止训练,会导致模型在极少量迭代后停止,根本未完成有效学习。正确做法是设置固定训练轮数,或监控训练集/验证集的平均误差下降情况。
  • 测试集使用错误:测试函数中误用X_train[j]作为测试样本,应改为X_test[j].flatten()。

修正后的核心训练代码示例:

def train(X_train, y_train, epochs=10):
    # Weight initialization
    weights = np.random.rand(X_vector_len, y_vector_len) * offset * 2 - offset
    bias = np.random.rand(y_vector_len) * offset * 2 - offset

    for epoch in range(epochs):
        total_error = 0.0
        # 随机打乱训练集(SGD标准操作)
        indices = np.random.permutation(len(X_train))
        for s in indices:
            sample = X_train[s].flatten()  # 展平为784维向量
            desired = np.zeros(y_vector_len, dtype=np.float64)
            desired[y_train[s]] = 1

            net = np.dot(sample, weights) + bias
            y_pred = activation_function(net)
            e_vec = desired - y_pred
            e = np.sum(e_vec ** 2) / 2
            total_error += e

            # 计算每个输出神经元的delta向量(元素级乘法)
            delta = -e_vec * acivation_derivative(net)
            # Numpy向量化更新权重:outer生成(784,10)梯度矩阵
            weights += learning_rate * np.outer(sample, delta)
            # 更新偏置
            bias += learning_rate * delta
        
        avg_error = total_error / len(X_train)
        print(f"Epoch {epoch+1}, Average Error: {avg_error:.4f}")
        # 可选:平均误差低于阈值时提前终止
        if avg_error < e_threshold:
            break
    return weights, bias, epoch+1

针对疑问的解答

1. 为何误差向量正确但神经网络无法学习?

核心原因是梯度计算和权重更新逻辑错误:你错误地将误差向量与导数向量做内积得到单一标量,用该标量统一更新所有输出神经元的权重,完全丢失了每个神经元的独立误差梯度信息。此外,样本未展平导致输入维度不匹配、训练终止条件不合理,也让模型根本没机会完成有效学习。

2. δ是内积得到的标量,δ_j具体是什么?若用δ替代δ_j是否会混淆权重更新?

δ_j是第j个输出神经元的误差项,公式为δ_j = -(d_j - y_j) * f'(net_j),是一个与输出神经元数量相同的向量(而非标量)。每个输出神经元j对应独立的δ_j,用于更新该神经元对应的所有输入权重(w_ij)。

用内积得到的标量替代δ_j是完全错误的:内积会把所有神经元的误差和导数混合成一个值,导致所有神经元的权重更新方向和幅度完全一致,无法针对每个类别的误差进行独立调整,这也是你的模型无法学习的关键原因。

3. Sigmoid值域为(0,1),误差向量范围为(-1,1),其他值域不同的激活函数(如f(x)=x、arctg(x))如何工作?

不同激活函数的适配逻辑核心在于激活值域与目标值范围的匹配:

  • 线性激活(f(x)=x):值域为(-∞,+∞),适合回归任务,若用于分类,需将目标值调整到对应范围(比如MNIST可设为[-5,5]区间的对应值),损失函数仍可用均方误差,但分类效果通常不如非线性激活。
  • arctg(x):值域为(-π/2, π/2),属于饱和非线性激活,导数始终为正且有界,目标值需映射到该值域内(比如将MNIST的目标1映射为π/2,0映射为-π/2),梯度更新逻辑与sigmoid一致,仅需替换激活函数和导数公式。

4. 单位阶跃函数是否适用于SGD?

单位阶跃函数不适用于SGD:它在x=0处不可导,其他位置导数为0,无法计算梯度,而SGD的核心依赖梯度下降来更新权重。如果硬要用,只能改用感知器的原始更新规则(基于分类错误更新,而非梯度),但这种规则仅适用于线性可分问题,MNIST是非线性可分的,所以效果极差。

5. 能否用Numpy函数实现权重更新(将w_ij行与x_i相乘)?

完全可以,这是推荐的高效做法(避免嵌套循环)。使用np.outer(sample, delta)即可实现:

  • sample是(784,)的输入向量,delta是(10,)的误差项向量
  • np.outer(sample, delta)会生成(784,10)的矩阵,每个元素为sample[i] * delta[j],正好对应权重更新的η * δ_j * x_i项
  • 直接执行weights += learning_rate * np.outer(sample, delta)即可完成所有权重的向量化更新,效率远高于嵌套循环。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 06:21:12