通过 ResNet 跳过连接的梯度反向传播

机器算法验证 机器学习 神经网络 卷积神经网络 梯度下降 反向传播
2022-02-05 05:52:27

我很好奇梯度如何通过使用 ResNet 模块/跳过连接的神经网络进行反向传播。我已经看到了一些关于 ResNet 的问题(例如,具有跳过层连接的神经网络),但这个问题专门询问了训练期间梯度的反向传播。

基本架构在这里:

在此处输入图像描述

我阅读了这篇论文,Study of Residual Networks for Image Recognition,在第 2 节中,他们讨论了 ResNet 的目标之一是如何让梯度反向传播到基础层的路径更短/更清晰。

谁能解释梯度如何通过这种类型的网络?我不太明白加法运算以及加法后缺少参数化层如何实现更好的梯度传播。它是否与梯度在流经加法运算符时不会改变并且以某种方式在没有乘法的情况下重新分配有关?

此外,如果梯度不需要流过权重层,我可以理解如何缓解梯度消失问题,但如果没有梯度流过权重,那么它们在反向传递后如何更新?

2个回答

添加将梯度平等地发送回两个输入。您可以通过在 tensorflow 中运行以下命令来说服自己:

import tensorflow as tf

graph = tf.Graph()
with graph.as_default():
    x1_tf = tf.Variable(1.5, name='x1')
    x2_tf = tf.Variable(3.5, name='x2')
    out_tf = x1_tf + x2_tf

    grads_tf = tf.gradients(ys=[out_tf], xs=[x1_tf, x2_tf])
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        fd = {
            out_tf: 10.0
        }
        print(sess.run(grads_tf, feed_dict=fd))

输出:

[1.0, 1.0]

所以,梯度将是:

  • 通过跳过层连接传递回之前的层,不变,并且
  • 传递给带有权重的块,并用于更新这些权重

编辑:有一个问题:“在图 2 的底部,高速公路连接和神经网络块再次连接在一起的点的操作是什么?”

答案是:它们相加。您可以从图 2 的公式中看到这一点:

outputF(x)+x

这就是说:

  • 总线中的值(x
  • 被添加到通过网络传递总线值的结果中,即xF(x)
  • 给出残差块的输出,我在这里将其标记为output

编辑2:

用稍微不同的词重写:

  • 在正向,输入数据沿总线向下流动
    • 在总线沿线的点,残差块可以学习向总线向量添加/删除值
  • 在向后的方向,梯度流回公共汽车
    • 一路上,梯度会更新它们经过的残差块
    • 残差块本身也会稍微修改梯度

残差块确实修改了向后流动的梯度,但没有梯度流过的“挤压”或“激活”功能。'squashing'/'activation' 函数是导致爆炸/消失梯度问题的原因,因此通过从总线本身移除这些函数,我们大大缓解了这个问题。

编辑 3:我个人认为我脑海中的 resnet 如下图所示。它在拓扑上与图 2 相同,但它可能更清楚地显示了总线如何直接流经网络,而剩余块只是从中提取值,并针对总线添加/删除一些小向量:

在此处输入图像描述

我想推荐这篇清晰的文章:CS231n Convolutional Neural Networks for Visual Recognition,让我将(简化的)香草网络与(简化的)残差网络进行比较,如下所示。

这是我从该页面借来的图表:

在此处输入图像描述

其中线条上方的绿色数字表示正向传递,红色数字表示反向传递(初始梯度为 1)。

让我们通过在某处添加一个残差来做一点改变来得到这个:

在此处输入图像描述

其中线条上方的蓝色数字表示正向传递,线条下方的红色数字表示反向传递(初始梯度为 1)。

这是最后一个示例的代码:

# use tensorflow 1.12
x = tf.Variable(3, name='x', dtype=tf.float32)                                                      
y = tf.Variable(-4, name='y', dtype=tf.float32)                                                     
z = tf.Variable(2, name='z', dtype=tf.float32)                                                      
w = tf.Variable(-1, name='w', dtype=tf.float32)                                                     
                                                                                  
x_multiply_y = tf.math.multiply(x, y, name="x_multiply_y")                        
z_max_w = tf.math.maximum(z, w, name="z_max_w")                                   
xy_plus_zw = tf.math.add(z_max_w, x_multiply_y, name="xy_plus_zw") 
residual_op = tf.math.add(x_multiply_y, xy_plus_zw, name="residual_op")
multiply_2 = tf.math.multiply(residual_op, 2, name="multiply_2")  
# to make sure that the last gradient is 1 we make the cost 1                 
cost = multiply_2 + 45

optimizer = tf.train.AdamOptimizer()                                              
variables = tf.trainable_variables() 
all_ops = variables + [x_multiply_y, z_max_w, xy_plus_zw, residual_op, multiply_2]
gradients = optimizer.compute_gradients(cost, all_ops)
init = tf.global_variables_initializer() 
sess = tf.Session()
sess.run(init)
variables = [g[1] for g in gradients]
gradients = [g[0] for g in gradients]
gradients = sess.run(gradients)
for var, gdt in zip(variables, gradients):
    print(var.name, "\t", gdt)
# the results are here:
# x:0    -16.0
# y:0    12.0
# z:0    2.0
# w:0    0.0
# x_multiply_y:0     4.0
# z_max_w:0      2.0
# xy_plus_zw:0   2.0
# residual_op:0      2.0
# multiply_2:0   1.0

我们可以看到梯度是从不同来源累积的。

HTH。