全连接神经网络反向传播实现求助:损失初期变化后停滞
全连接神经网络反向传播与实现错误排查
以下是针对你代码中导致损失停滞的关键错误点,以及对应的修正方案:
一、激活函数实现错误
1. Sigmoid导数递归调用死循环
原代码中sigmoid的导数实现会无限递归调用自身,导致无法正确计算导数:
def sigmoid(x: np.ndarray, derivative=False) -> np.ndarray: return sigmoid(x) * (1. - sigmoid(x)) if derivative else 1. / (1. + np.exp(-x))
修正方案:直接使用sigmoid的输出值计算导数,避免递归:
def sigmoid(x: np.ndarray, derivative=False) -> np.ndarray: sig = 1. / (1. + np.exp(-x)) return sig * (1. - sig) if derivative else sig
2. ReLU的条件判断与输入修改错误
- 原代码用
~derivative判断,Python中~是按位取反,布尔值True取反为-2(仍视为真),导致导数分支永远不会执行; - 修改输入数组
x会破坏前向传播中存储的Z值,影响后续计算。
原错误代码:
def relu(x: np.ndarray, derivative=False) -> np.ndarray: if ~derivative: return np.maximum(0, x) x[x<=0] = 0 x[x>0] = 1 return x
修正方案:
def relu(x: np.ndarray, derivative=False) -> np.ndarray: if not derivative: return np.maximum(0, x) # 创建副本避免修改原数据 x_copy = x.copy() x_copy[x_copy <= 0] = 0 x_copy[x_copy > 0] = 1 return x_copy
二、损失函数的小问题
原MSE函数返回的是平方误差而非均值,虽然不是损失停滞的核心原因,但不符合函数命名:
def mse(y: np.ndarray, y_pred: np.ndarray, derivative=False) -> np.ndarray: if derivative: return 2 * (y_pred - y) # 加入均值计算 return np.mean(np.square(y_pred - y))
三、神经网络类核心错误
1. 缺少偏置(Bias)初始化与计算
全连接层必须包含偏置项,原代码仅初始化权重W,导致模型表达能力严重不足:
修正random_initializers:
def random_initializers(self) -> None: self.num_layers = len(self.layer_sizes)-1 self.parameters = {} for i in range(self.num_layers): # 初始化权重 self.parameters[f"W{i+1}"] = np.random.randn(self.layer_sizes[i+1], self.layer_sizes[i]) * np.sqrt(1. / self.layer_sizes[i]) # 初始化偏置 self.parameters[f"b{i+1}"] = np.zeros((self.layer_sizes[i+1], 1))
2. 前向传播缺少偏置项
原forward中Z的计算未加偏置,修正后:
def forward(self, X: np.ndarray) -> np.ndarray: # 确保输入为列向量,适配矩阵运算 self.parameters['A0'] = X.reshape(-1, 1) for i in range(self.num_layers): W = self.parameters[f"W{i+1}"] b = self.parameters[f"b{i+1}"] self.parameters[f"Z{i+1}"] = np.dot(W, self.parameters[f"A{i}"]) + b self.parameters[f"A{i+1}"] = self.activation_functions[i](self.parameters[f"Z{i+1}"]) return self.parameters[f"A{self.num_layers}"]
3. 反向传播核心逻辑错误
原反向传播的误差初始化、梯度计算、循环索引全部存在问题:
修正backward函数:
def backward(self, loss_prime): update = {} num_layers = self.num_layers # 输出层误差:损失导数 × 输出层激活函数导数 delta = loss_prime * self.activation_functions[-1](self.parameters[f"Z{num_layers}"], derivative=True) # 输出层权重与偏置的梯度 update[f"W{num_layers}"] = np.dot(delta, self.parameters[f"A{num_layers-1}"].T) update[f"b{num_layers}"] = delta # 从倒数第二层反向遍历到第一层 for i in range(num_layers-1, 0, -1): # 当前层误差:上一层权重转置 × 上一层误差 × 当前层激活函数导数 delta = np.dot(self.parameters[f"W{i+1}"].T, delta) * self.activation_functions[i-1](self.parameters[f"Z{i}"], derivative=True) # 当前层权重与偏置的梯度 update[f"W{i}"] = np.dot(delta, self.parameters[f"A{i-1}"].T) update[f"b{i}"] = delta return update
4. 权重更新逻辑错误
原代码遍历权重数组的每一行更新,逻辑冗余且可能出错,直接整体更新即可:
修正update_weights:
def update_weights(self, update: dict[str, np.ndarray]) -> None: for key, value in update.items(): self.parameters[key] -= self.learning_rate * value
5. 训练循环的细节问题
- 原
fit中未将每个epoch的平均损失存入历史记录; - 确保输入数据形状正确,且每个epoch后重置损失值:
def fit(self, X: np.ndarray, y: np.ndarray ) -> None: self.hist = {"loss": [], "frame": []} self.random_initializers() # 确保y为列向量 y = y.reshape(-1, 1) for epoch in tqdm(range(self.epochs)): total_loss = 0.0 for i, x in enumerate(X): pred = self.forward(x) # 累加单个样本损失 total_loss += self.loss(y[i], pred) # 损失对预测值的导数 loss_prime = self.loss(y[i], pred, derivative=True) update = self.backward(loss_prime) self.update_weights(update) # 记录当前epoch的平均损失 avg_loss = total_loss / len(X) self.hist["loss"].append(avg_loss)
其他注意事项
- 确认
learning_rate已在类中定义(原代码未显示,需确保初始化时传入或设置默认值); - 如果使用ReLU激活,建议改用He初始化(即
np.sqrt(2. / self.layer_sizes[i]))调整权重方差,避免神经元死亡; - 若仍存在损失停滞,可尝试调整学习率、增加模型层数/神经元数量,或检查输入数据是否做了归一化处理。
内容的提问来源于stack exchange,提问作者Moe
相关产品推荐
相关产品推荐

