relu\max pooling\argmax是如何进行反向传导的

众所周知,在神经网络中,进行反向传导需要计算梯度,这也就要求函数本身可导,但是relu、max pooling、argmax都是不可导的函数,既然不可导(或者不连续可导),也就无法求解梯度(或者无法在某一点上求解梯度),那这些函数是如何实现反向更新参数的呢?

Relu

在这里插入图片描述
在这里插入图片描述
Relu在x=0处是不可导的,其采用的方法是次梯度.

在这里插入图片描述
对于ReLU函数, 当x>0的时候,其导数为1; 当x<0时,其导数为0. 则ReLU函数在x=0的次梯度是 [0,1] ,这里是次梯度有多个,可以取0,1之间的任意值. 工程上为了方便取c=0即可.

max pooling

对于max pooling 而言,对于非极大值而言是没有梯度的,因为轻微地改变它们并不影响结果,此外,max可以近似看作斜率为1,因为输出的值等于max.因此,梯度是从下一层当中反向传播到取得最大值的神经元上的,所有其他的神经元梯度值为0。
在这里插入图片描述
这张图浅显易懂,但是关于1.0,0.3,0.9,0.5这些梯度值的来源,我认为是上一层回传回来的梯度,跟本层没有关系,本层如果不考虑上层回传回来的梯度的话,梯度均为1.

argmax

argmax的反向梯度求导跟max pooling有些相似之处,但也有所不同,因为argmax是取出最大值所在的位置,先来看一个例子:

t = torch.tensor([-0.0627,  0.1373,  0.0616, -1.7994,  0.8853, 
                  -0.0656,  1.0034,  0.6974,  -0.2919, -0.0456])
torch.argmax(t).item() # outputs 6

我们增加t[6]的值或者降低其他元素的值都不会改变最终的结果,从这个角度来说,整层的梯度都为0,这一层都可以忽略不计,如果是这样的话,整个网络都无法进行梯度回传了.

针对argmax的梯度回传,知乎上有个说法:
strainght through estimator, 可以看一下~大概思想就是: 假设输入的向量是v, 那么我们用softmax得到softmax(v). 这样, 最大值那个地方就会变得很靠近1, 其他地方就会变得很靠近0. 然后, 我们计算argmax(v), 接着可以得到一个常数c = argmax(v) - softmax(v). 我们这时, 可以用softmax(v) + c来作为argmax(v)的结果. 这个东西的好处是, 我们的softmax(v) + c是有反向传播的能力的. 换句话说, 我们用softmax(v)的梯度来作为反向传播.
听上去很通俗易懂,但是没有实际操作过。

在实际项目中,我们找到了一种逼近argmax的可导函数,采用此方法有一个很强的先验:我们需要知道要求取的最大值的数值是多少。比如,在这个例子当中:我们要向网络输入两张图,其中一张图中关键点的位置(x1,y1)距离另一张图关键点的位置(x2,y2)的距离是800,所以我们在损失函数那里需要从网络输出的置信度图中求取最大值把两个点的位置取出来,算出距离是否满足限制(计算loss),我们知道关键点的置信度图是一个高斯置信度图,我们让这个高斯尽可能地尖,
在这里插入图片描述
我们知道最高值大概是0.98,我们再设计一个0.98的阈值化函数,让0.98以上的值为1,0.98以下的值为0,这样就能取出最大值。
Sigmoid 函数:
在这里插入图片描述
sigmoid是可微可导的函数,(等同于二分类的softmax),如果将中间梯度为非0的地方变得陡峭,可以等同地将其看作一个二值化的函数。(感觉这种函数也存在问题,相当于只有中间小部分梯度为非0,其余地方梯度仍是0,当然,也要看实际数据分布的情况,如果数据分布主要集中在中间,则梯度回传也是非常多的)。
取点的位置
其实二值化取出来的不一定是一个点,可能是大于一定值的好几个点,只要这几个点都聚集在一起,我们可以将置信度图经过全图的总和值之后,分别乘以x索引和y索引,然后累加获取,max_loc_x以及max_loc_y,运用此种方法取点的位置是可导的。
如下图的代码所示:

import os
import torch
import numpy as np
#这里是需要进行归一化
x=np.array([[0,0,0,0,0],[0,0,0.9,0,0],[0,0,0,0,0]])
x_sum=x.sum()
print(x)
print("x_sum:",x_sum)
print("-------------")
x=x.reshape(1,15)
y=torch.Tensor(x)
y=y.unsqueeze(0)
y=y.unsqueeze(0)
print("y.size()",y.size())
print("y_origin--------")
print(y)
y=y/x_sum
y=y.reshape([1,1,3,5])
print(y)
print(y.sum())
#制造x_index
x_index=[]
for i in range(3):
    temp=np.arange(5)
    x_index.append(temp)
x_index=np.array(x_index)
print(x_index)
y_index=[]
for i in range(5):
    temp=np.arange(3)
    y_index.append(temp)
y_index=np.array(y_index)
y_index=y_index.transpose()
print(y_index)
y=y.squeeze(0)
y=y.squeeze(0)
x_loc_map=y*x_index
y_loc_map=y*y_index
x_loc=x_loc_map.sum()
y_loc=y_loc_map.sum()
print("x_loc",x_loc)
print("y_loc",y_loc)

此时的输出结果为:

[[0.  0.  0.  0.  0. ]
 [0.  0.  0.9 0.  0. ]
 [0.  0.  0.  0.  0. ]]
x_sum: 0.9
-------------
y.size() torch.Size([1, 1, 1, 15])
y_origin--------
tensor([[[[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.9000,
           0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]]]])
tensor([[[[0., 0., 0., 0., 0.],
          [0., 0., 1., 0., 0.],
          [0., 0., 0., 0., 0.]]]])
tensor(1.)
[[0 1 2 3 4]
 [0 1 2 3 4]
 [0 1 2 3 4]]
[[0 0 0 0 0]
 [1 1 1 1 1]
 [2 2 2 2 2]]
x_loc tensor(2., dtype=torch.float64)
y_loc tensor(1., dtype=torch.float64)

需要注意的是,当前例子中,我们输入的是比较理想的输入:

[[0.  0.  0.  0.  0. ]
 [0.  0.  0.9 0.  0. ]
 [0.  0.  0.  0.  0. ]]

当我们的输入稍微换一下,

[[0.8  0.  0.  0.  0. ]
 [0.  0.  0.9 0.  0. ]
 [0.  0.  0.  0.  0. ]]

得到的结果是:

x_loc tensor(1.0588, dtype=torch.float64)
y_loc tensor(0.5294, dtype=torch.float64)

由此可以看出,这种使用期望的方式并不能很好地反应最大值的位置,尤其在有很多非零值干扰,或者其他较大值散落分布的情况下。所以,前期利用sigmoid去掉大部分干扰值还是很有必要的。

猜你喜欢

转载自blog.csdn.net/weixin_39326879/article/details/115629202