基于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
相关产品推荐
相关产品推荐

