实现带动量的SGD优化器时遭遇NumPy广播形状不匹配错误
问题:带动量的SGD优化器更新参数时出现NumPy广播错误
按照《Neural Networks from Scratch in Python》一书的指导实现神经网络,在为带学习率衰减的SGD优化器添加动量时,出现如下错误:
Traceback (most recent call last): File "nn.py", line 142, in <module> optimizer.update_params(dense1) File "nn.py", line 110, in update_params layer.biases += bias_updates ValueError: non-broadcastable output operand with shape (1,64) doesn't match the broadcast shape (2,64)
以下是完整代码:
import numpy as np from nnfs.datasets import spiral_data import nnfs import pickle nnfs.init() class Layer_Dense: def __init__(self, n_inputs, n_neurons): self.weights = 0.01 * np.random.randn(n_inputs, n_neurons) self.biases = np.zeros((1, n_neurons)) def forward(self, inputs): self.output = np.dot(inputs, self.weights) + self.biases self.inputs = inputs def backward(self, dvalues): self.dweights = np.dot(self.inputs.T, dvalues) self.dbiases = np.sum(dvalues, axis=0, keepdims=True) self.dinputs = np.dot(dvalues, self.weights.T) class Activation_ReLU: def forward(self, inputs): self.output = np.maximum(0, inputs) self.inputs = inputs def backward(self, dvalues): self.dinputs = dvalues.copy() self.dinputs[self.inputs <= 0] = 0 class Activation_Softmax: def forward(self, inputs): exp_values = np.exp(inputs - np.max(inputs, axis=1, keepdims=True)) probabilities = exp_values / np.sum(exp_values, axis=1, keepdims=True) self.output = probabilities def backward(self, dvalues): self.dinputs = np.empty_like(dvalues) for index, (single_output, single_dvalues) in enumerate(zip(self.output, dvalues)): single_output = single_output.reshape(-1, 1) jacobian_matrix = np.diagflat(single_output) - np.dot(single_output,single_output.T) self.dinputs[index] = np.dot(jacobian_matrix, single_dvalues) class Loss: def calculate(self, output, y): sample_losses = self.forward(output, y) data_loss = np.mean(sample_losses) return data_loss class Loss_CategoricalCrossEntropy(Loss): def forward(self, y_pred, y_true): samples = len(y_pred) y_pred_clipped = np.clip(y_pred, 1e-7, 1-1e-7) if len(y_true.shape) == 1: correct_confidences = y_pred_clipped[ range(samples), y_true ] elif len(y_true.shape) == 2: correct_confidences = np.sum( y_pred_clipped * y_true, axis=1 ) negative_log_likelihoods = -np.log(correct_confidences) return negative_log_likelihoods def backward(self, dvalues, y_true): samples = len(dvalues) labels = len(dvalues[0]) if len(y_true.shape) == 1: y_true = np.eye(labels)[y_true] self.dinputs = -y_true / dvalues self.dinputs = self.dinputs / samples class Activation_Softmax_Loss_CategoricalCrossEntropy(): def __init__(self): self.activation = Activation_Softmax() self.loss = Loss_CategoricalCrossEntropy() def forward(self, inputs, y_true): self.activation.forward(inputs) self.output = self.activation.output return self.loss.calculate(self.output, y_true) def backward(self, dvalues, y_true): samples = len(dvalues) if len(y_true.shape) == 2: y_true = np.argmax(y_true, axis=1) self.dinputs = dvalues.copy() self.dinputs[range(samples), y_true] -= 1 self.dinputs = self.dinputs / samples class Optimizer_SGD: def __init__(self, learning_rate=1., decay=0., momentum=0.): self.learning_rate = learning_rate self.current_learning_rate = learning_rate self.decay = decay self.iterations = 0 self.momentum = momentum def pre_update_params(self): if self.decay: self.current_learning_rate = self.learning_rate * (1. / (1. + self.decay * self.iterations)) def update_params(self, layer): if self.momentum: if not hasattr(layer, 'weight_momentums'): layer.weight_momentums = np.zeros_like(layer.weights) layer.bias_momentums = np.zeros_like(layer.biases) weight_updates = self.momentum * layer.weight_momentums - self.current_learning_rate * layer.dweights layer.weight_momentums = weight_updates bias_updates = self.momentum * layer.bias_momentums - self.current_learning_rate * layer.dweights layer.bias_momentums = bias_updates else: weight_updates = -self.current_learning_rate * layer.dweights bias_updates = -self.current_learning_rate * layer.dbiases layer.weights += weight_updates layer.biases += bias_updates def post_update_params(self): self.iterations += 1 X, y = spiral_data(samples=100, classes=3) optimizer = Optimizer_SGD(decay=1e-3, momentum=0.5) dense1 = Layer_Dense(2, 64) activation1 = Activation_ReLU() dense2 = Layer_Dense(64, 3) loss_activation = Activation_Softmax_Loss_CategoricalCrossEntropy() for epoch in range(10001): dense1.forward(X) activation1.forward(dense1.output) dense2.forward(activation1.output) loss = loss_activation.forward(dense2.output, y) predictions = np.argmax(loss_activation.output, axis=1) if len(y.shape) == 2: y = np.argmax(y, axis=1) accuracy = np.mean(predictions==y) if not epoch % 100: print(f'epoch: {epoch}, acc: {accuracy:.3f}, loss: {loss:.3f}, lr: {optimizer.current_learning_rate}') loss_activation.backward(loss_activation.output, y) dense2.backward(loss_activation.dinputs) activation1.backward(dense2.dinputs) dense1.backward(activation1.dinputs) optimizer.pre_update_params() optimizer.update_params(dense1) optimizer.update_params(dense2) optimizer.post_update_params() stream = [dense1.weights, dense1.biases, dense2.weights, dense2.biases] with open("trained.nn", "wb") as h: pickle.dump(stream, h)
注:已明确pickle并非最佳方案,仅为实现简便选用。
问题分析与解决
错误根源在Optimizer_SGD类的update_params方法中,计算偏置更新量时误用了权重的梯度矩阵layer.dweights,而非偏置的梯度矩阵layer.dbiases:
原错误代码行:
bias_updates = self.momentum * layer.bias_momentums - self.current_learning_rate * layer.dweights
对于dense1层,layer.dweights的形状是(2,64),而layer.biases的形状是(1,64),两者形状不匹配,导致NumPy尝试广播时失败,抛出non-broadcastable output operand错误。
修正方法:将layer.dweights替换为layer.dbiases,因为layer.dbiases是在Layer_Dense的backward方法中计算的,形状为(1, n_neurons),与layer.biases的形状完全一致,不会触发广播错误。
修正后的代码行:
bias_updates = self.momentum * layer.bias_momentums - self.current_learning_rate * layer.dbiases
替换后,优化器可以正确计算偏置的更新量,与权重更新逻辑保持一致,解决形状不匹配的问题。
内容的提问来源于stack exchange,提问作者A.K _ThePortal
相关产品推荐
相关产品推荐

