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

基于NumPy的分类神经网络无法收敛,第一层权重无变化求助

问题:NumPy手动实现的分类神经网络无法收敛,第一层权重完全无更新

我正在仅使用NumPy库构建一个分类神经网络,已完成网络搭建并梳理了逻辑,自认为逻辑无问题,但模型无法收敛到最优参数值。我注意到一个关键问题:第一层的权重完全没有变化。

我已检查误差值和运算维度,未发现问题;尝试过不同学习率、批次大小,用TensorFlow搭建相同结构能得到良好预测结果,说明模型结构无问题;也尝试过random.randn、全零等不同参数初始化方式。

import numpy as np
from tensorflow.keras.datasets import mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train = x_train.reshape(x_train.shape[0], -1)
x_test = x_test.reshape(x_test.shape[0], -1)
x_train, x_test = x_train/255, x_test/255
print(x_train.shape)

from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(sparse_output=False)
ohe.fit(y_train.reshape(-1,1))
y_train = ohe.fit_transform(y_train.reshape(-1,1))
y_test = ohe.transform(y_test.reshape(-1,1))
print(y_train.shape)

def linear(x,deriv=False):
    return x

def relu(x,deriv=False):
    if deriv:
        return (x > 0).astype(float)
    return np.maximum(0,x)

def softmax(x,deriv=False):
    xre = x - x.max(axis=0,keepdims=True)
    xexp = np.exp(xre)
    a = xexp.sum(axis=0,keepdims=True)
    prob = xexp/a
    return prob

def sigmoid(x,deriv=False):
    a = 1/(1+np.exp(-x))
    if deriv:
        return a * (1 - a)
    return a

activations = {'linear':linear,'relu':relu,'sigmoid':sigmoid,'softmax':softmax}

def initialvals(cols=784):
    shape = [cols,10,10]
    w = dict()
    b = dict()
    for i in range(len(shape)-1):
        w[i+1] = np.random.uniform(-0.5,0.5,(shape[i+1],shape[i]))

    for i in range(len(shape)-1):
        b[i+1] = np.zeros((shape[i+1],1))
    return w,b

def allprints(ww,bb):
    print('Weights')
    for i in ww:
        print(ww[i].shape)
    print('Biases')
    for i in bb:
        print(bb[i].shape)

    print('Weights')
    for i in ww:
        print(i)
        print(ww[i])
        print()
    print('Biases')
    for i in bb:
        print(i)
        print(bb[i])
        print()

def forprop(inputs,weight,bias,acts,av):
    z = dict()
    a = dict()
    
    z[0] = inputs.T
    a[0] = acts[av[0]](z[0])
    
    for i in range(1,len(weight)+1):
        z[i] = np.dot(weight[i],a[i-1]) + bias[i]
        a[i] = acts[av[i]](z[i])
    return z,a

def backprop(inputs, output, weight, bias, acts, av, size=50, iters=20, lr=0.01):
    n_samples = inputs.shape[0]
    global z_in
    for k in range(iters):
        shuff = np.random.permutation(n_samples)
        inputs = inputs[shuff]
        output = output[shuff]
        
        for i in range(0, n_samples, size): 
            batch_inputs = inputs[i:i + size]
            batch_output = output[i:i + size]
            z, a = forprop(batch_inputs, weight, bias, acts, av)
            z_in = z
            er = dict()
            er[len(bias)] = a[len(bias)] - batch_output.T 
            for j in range(len(bias)-1, 0, -1):
                er[j] = np.dot(weight[j + 1].T, er[j + 1]) * acts[av[j]](z[j], deriv=True)
                # delta_h = np.transpose(w_h_o) @ delta_o * (h * (1 - h))

            for j in range(1, len(bias) + 1):
                bias[j] -= lr * er[j].mean(axis=1, keepdims=True)
                weight[j] -= lr * (np.dot(er[j], a[j - 1].T) / batch_inputs.shape[0])
    return weight, bias, er ,z_in

we,be = initialvals()
allprints(we,be)
w_calc, b_calc, er, z= backprop(x_train,y_train,we,be,activations,['linear','sigmoid','softmax'])
allprints(w_calc,b_calc)

问题根源与修复方案

1. 核心问题:权重初始化导致sigmoid激活饱和,梯度消失

你使用np.random.uniform(-0.5, 0.5)初始化第一层权重(维度10×784),这种初始化方式会导致隐藏层的加权和z1 = w1@a0 + b1数值过大:输入是0-1的像素值,784个输入乘以0.5的权重,求和后可达392左右,直接让sigmoid函数进入饱和区。

sigmoid函数在输入绝对值过大时,导数趋近于0,导致隐藏层的误差项er[1] = w2.T@er2 * sigmoid_deriv(z1)几乎为0,最终第一层权重的更新幅度极小,看起来完全没有变化。

而TensorFlow默认使用Xavier/He等自适应初始化方法,避免了激活函数饱和,因此相同结构可以正常收敛。

2. 次要问题:softmax函数未实现导数(当前不影响误差计算,但需修复完整性)

你的softmax函数未处理deriv=True的情况,虽然交叉熵损失+softmax的组合下,误差项可以直接用a-y而不需要计算softmax导数,但为了函数完整性需要补充实现。


具体修复步骤

步骤1:替换为Xavier初始化(适配sigmoid激活)

修改initialvals函数的权重初始化逻辑,使用Xavier初始化(针对sigmoid/tanh等S型激活):

def initialvals(cols=784):
    shape = [cols,10,10]
    w = dict()
    b = dict()
    for i in range(len(shape)-1):
        # Xavier初始化:标准差为1/√输入维度
        w[i+1] = np.random.randn(shape[i+1], shape[i]) * np.sqrt(1 / shape[i])

    for i in range(len(shape)-1):
        b[i+1] = np.zeros((shape[i+1],1))
    return w,b

步骤2:修复softmax函数的导数实现

def softmax(x,deriv=False):
    xre = x - x.max(axis=0,keepdims=True)
    xexp = np.exp(xre)
    a = xexp.sum(axis=0,keepdims=True)
    prob = xexp/a
    if deriv:
        # softmax批量导数:prob * (np.eye(prob.shape[0])[:, :, None] - prob)
        # 但交叉熵损失下无需直接使用,此处仅作完整实现
        return prob * (1 - prob)
    return prob

步骤3:可选优化:调整学习率或更换激活函数

  • 结合Xavier初始化后,可尝试将学习率提高至0.1左右,加快收敛速度;
  • 若想彻底避免sigmoid饱和问题,可将隐藏层激活替换为ReLU,并使用He初始化(标准差为√2/√输入维度)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 06:25:55