为何在TensorFlow中需对网络权重与偏置执行两次更新?
问题解释:两次参数更新的原因及代码问题
问题描述
我用TensorFlow实现了DenseLayer和DenseNetwork类,但搞不懂为什么要对权重和偏置做两次更新:一次在DenseLayer的backward方法里,另一次在DenseNetwork的train方法里。一开始以为是笔误,结果移除第二次更新后损失就不再下降了,想知道这是为什么。
实现代码
class DenseLayer(tf.Module): def __init__(self, input_size, output_size): super(DenseLayer, self).__init__() self.input_size = input_size self.output_size = output_size self.weights = tf.Variable(tf.random.normal([input_size, output_size], stddev=0.01)) self.biases = tf.Variable(tf.zeros([output_size])) def __call__(self, inputs): return self.forward(inputs) def forward(self, inputs): self.last_input = inputs self.z = tf.matmul(inputs, self.weights) + self.biases return self.z def backward(self, d_L_d_out, learning_rate): with tf.GradientTape(persistent=True) as tape: d_L_d_inputs = tf.matmul(d_L_d_out, tf.transpose(self.weights)) d_L_d_weights = tape.gradient(d_L_d_inputs, self.weights) d_L_d_biases = tape.gradient(d_L_d_inputs, self.biases) self.weights.assign_sub(learning_rate * d_L_d_weights) self.biases.assign_sub(learning_rate * d_L_d_biases) return d_L_d_inputs class DenseNetwork(tf.Module): def __init__(self): super(DenseNetwork, self).__init__() self.layers = [] def add_layer(self, layer): self.layers.append(layer) def __call__(self, inputs): return self.forward(inputs) def forward(self, inputs): for layer in self.layers: inputs = layer.forward(inputs) return inputs def backward(self, d_L_d_out, learning_rate): for layer in reversed(self.layers): d_L_d_out = layer.backward(d_L_d_out, learning_rate) def train(self, inputs, targets, learning_rate, epochs): for epoch in range(epochs): loss = 0 with tf.GradientTape(persistent=True) as tape: output = self.forward(inputs) loss = tf.reduce_mean(tf.square(output - targets)) grads = tape.gradient(loss, self.trainable_variables) for i, layer in enumerate(self.layers): layer.biases.assign(layer.biases - learning_rate*grads[2*i]) layer.weights.assign(layer.weights - learning_rate * grads[2*i+1]) if (epoch + 1) % 10 == 0: tf.print(f"Epoch {epoch+1} - Loss: {loss}") def loss(self, output, target): return tf.reduce_mean(tf.square(output - target))
核心原因:第一次更新完全错误
你遇到的问题本质是DenseLayer的backward方法里的梯度计算逻辑完全错误,第一次更新根本没有起到正确的反向传播优化作用,只有train方法里的第二次更新是有效的。
具体分析:
- 正确的全连接层梯度计算:
- 权重梯度应该是
tf.matmul(tf.transpose(self.last_input), d_L_d_out)(输入的转置乘输出的损失梯度) - 偏置梯度应该是
tf.reduce_sum(d_L_d_out, axis=0)(输出损失梯度在样本维度求和)
- 权重梯度应该是
- 你的backward方法错误点:
你用GradientTape包裹d_L_d_inputs = tf.matmul(d_L_d_out, tf.transpose(self.weights)),然后求d_L_d_inputs对权重的梯度——这个梯度和“损失对权重的梯度”完全无关,只是一个数学上的偏导数值,用它更新参数等于瞎调整,根本无法让损失下降。 - 为什么移除第二次更新后损失不下降:
因为第一次更新是无效的,只有train方法里通过GradientTape追踪整个前向传播和损失计算得到的梯度才是正确的,这部分更新才真正在优化模型。现在两次更新并存时,第二次正确的更新抵消了第一次错误更新的影响,模型还能正常优化;一旦去掉第二次,只剩下错误的更新,模型自然无法学习。
修正方案
你需要二选一,统一参数更新的逻辑:
方案1:保留train方法的自动梯度更新,去掉backward里的参数更新
修改DenseLayer的backward方法,只负责计算传递给上一层的梯度,不更新参数:
def backward(self, d_L_d_out): # 只计算对输入的梯度,传递给上一层 d_L_d_inputs = tf.matmul(d_L_d_out, tf.transpose(self.weights)) return d_L_d_inputs
train方法保持不变,继续用自动梯度更新参数。
方案2:用手动计算梯度的方式在backward里更新参数,去掉train里的更新
修正DenseLayer的backward方法,用正确的手动梯度计算更新参数:
def backward(self, d_L_d_out, learning_rate): # 正确计算权重和偏置的梯度 d_L_d_weights = tf.matmul(tf.transpose(self.last_input), d_L_d_out) d_L_d_biases = tf.reduce_sum(d_L_d_out, axis=0) # 更新参数 self.weights.assign_sub(learning_rate * d_L_d_weights) self.biases.assign_sub(learning_rate * d_L_d_biases) # 计算对输入的梯度,传递给上一层 d_L_d_inputs = tf.matmul(d_L_d_out, tf.transpose(self.weights)) return d_L_d_inputs
然后修改train方法,调用backward完成更新:
def train(self, inputs, targets, learning_rate, epochs): for epoch in range(epochs): with tf.GradientTape() as tape: output = self.forward(inputs) loss = self.loss(output, targets) # 计算损失对模型输出的梯度 d_L_d_out = tape.gradient(loss, output) # 反向传播更新所有层的参数 self.backward(d_L_d_out, learning_rate) if (epoch + 1) % 10 == 0: tf.print(f"Epoch {epoch+1} - Loss: {loss}")
内容的提问来源于stack exchange,提问作者unit 1991
相关产品推荐
相关产品推荐

