为何要做梯度检查?

神经网络算法使用反向传播计算目标函数关于每个参数的梯度,可以看做解析梯度。由于计算过程中涉及到的参数很多,用代码实现的反向传播计算的梯度很容易出现误差,导致最后迭代得到效果很差的参数值。

为了确认代码中反向传播计算的梯度是否正确,可以采用梯度检验(gradient check)的方法。通过计算数值梯度,得到梯度的近似值,然后和反向传播得到的梯度进行比较,若两者相差很小的话则证明反向传播的代码是正确无误的。

数值微分

导数概念回忆

f’(x)=\lim_{h \to 0} {f(x+h)-f(x) \over h} \tag{1}

其含义就是x的微小变化h(h为无限小的值),会导致函数f(x)的值有多大变化。在Python中可以这样实现:

  1. def numerical_diff(f, x):
  2. h = 1e-5
  3. d = (f(x+h) - f(x))/h
  4. return d

因为计算机的舍入误差的原因,h不能太小,比如1e-10,会造成计算结果上的误差,所以我们一般用[1e-4,1e-7]之间的数值。

但是如果使用上述方法会有一个问题,如图12-4所示。

梯度检查 - 图1

红色实线为真实的导数切线,蓝色虚线是上述方法的体现,即从x到x+h画一条直线,来模拟真实导数。但是可以明显看出红色实线和蓝色虚线的斜率是不等的。因此我们通常用绿色的虚线来模拟真实导数,公式变为:

f’(x) = \lim_{h \to 0} {f(x+h)-f(x-h) \over 2h} \tag{2}

公式2被称为双边逼近方法。

用双边逼近形式会比单边逼近形式的误差小100~10000倍左右,可以用泰勒展开来证明。

泰勒公式

泰勒公式是将一个在x=x_0处具有n阶导数的函数f(x)利用关于(x-x_0)的n次多项式来逼近函数的方法。若函数f(x)在包含x_0的某个闭区间[a,b]上具有n阶导数,且在开区间(a,b)上具有(n+1)阶导数,则对闭区间[a,b]上任意一点x,下式成立:

f(x)={f(x_0) \over 0!} + {f’(x_0) \over 1!}(x-x_0)+{f’’(x_0) \over 2!}(x-x_0)^2 + …+{f^{(n)}(x_0) \over n!}(x-x_0)^n+R_n(x) \tag{3}

其中, 表示f(x)的n阶导数,等号后的多项式称为函数f(x)x_0处的泰勒展开式,剩余的R_n(x)是泰勒公式的余项,是(x-x_0)^n的高阶无穷小。

利用泰勒展开公式,令x=\theta + h, x_0=\theta,我们可以得到:

f(\theta + h)=f(\theta) + f’(\theta)h + O(h^2) \tag{4}

单边逼近误差

如果用单边逼近,把公式4两边除以h后变形:

f’(\theta) + O(h)={f(\theta+h)-f(\theta) \over h} \tag{5}

公式5已经和公式1的定义非常接近了,只是左侧多出来的第二项,就是逼近的误差,是个O(h)级别的误差项。

双边逼近误差

如果用双边逼近,我们用三阶泰勒展开:

x=\theta + h, x_0=\theta,我们可以得到:

f(\theta + h)=f(\theta) + f’(\theta)h + f’’(\theta)h^2 + O(h^3) \tag{6}

再令x=\theta - h, x_0=\theta我们可以得到:

f(\theta - h)=f(\theta) - f’(\theta)h + f’’(\theta)h^2 - O(h^3) \tag{7}

公式6减去公式7,有:

f(\theta + h) - f(\theta - h)=2f’(\theta)h + 2O(h^3) \tag{8}

两边除以2h:

f’(\theta) + O(h^2)={f(\theta + h) - f(\theta - h) \over 2h} \tag{9}

公式9中,左侧多出来的第二项,就是双边逼近的误差,是个O(h^2)级别的误差项,比公式5中的误差项小很多数量级。

实例说明

公式2就是梯度检查的理论基础。比如一个函数:

$$
f(x) = x^2 + 3x
$$

我们看一下它在x=2处的数值微分,令h = 0.001:

f(x+h) = f(2+0.001) = (2+0.001)^2 + 3 \times (2+0.001) =10.007001 f(x-h) = f(2-0.001) = (2-0.001)^2 + 3 \times (2-0.001) =9.993001 {f(x+h)-f(x-h) \over 2h}={10.007001-9.993001 \over 2 \times 0.001}=7 \tag{10}

再看它的数学解析解:

$$
f’(x)=2x+3
$$

f’(x|_{x=2})=2 \times2+3=7 \tag{11}

可以看到公式10和公式11的结果一致。当然在实际应用中,一般不可能会完全相等,只要两者的误差小于1e-5以下,我们就认为是满足精度要求的。

算法实现

在神经网络中,我们假设使用多分类的交叉熵函数,则其形式为:

$$
J(w,b) =- \sum{i=1}^m \sum{j=1}^n y{ij} \log a{ij}
$$

m是样本数,n是分类数。

参数向量化

我们需要检查的是关于W和B的梯度,而W和B是若干个矩阵,而不是一个标量,所以在进行梯度检验之前,我们先做好准备工作,那就是把矩阵W和B向量化,然后把神经网络中所有层的向量化的W和B连接在一起(concatenate),成为一个大向量,我们称之为J(theta),然后对通过back-prop过程得到的W和B求导的结果d\theta_{real}也做同样的变换,接下来我们就要开始做检验了。

向量化的W,B连接以后,统一称作为\theta,按顺序用不同下标区分,于是有J(theta)的表达式为:

$$
J(w,b)=J(\theta_1,…,\theta_i,…,\theta_n)
$$

对于上式中的每一个向量,我们依次使用公式2的方式做检查,于是有对第i个向量值的梯度检查公式:

$$
{\partial J \over \partial \theta_i}={J(\theta_1,…,\theta_i+h,…,\theta_n) - J(\theta_1,…,\theta_i-h,…,\theta_n) \over 2 h}
$$

因为我们是要比较两个向量的对应分量的差别,这个可以用对应分量差的平方和的开方(欧氏距离)来刻画。但是我们不希望得到一个具体的刻画差异的值,而是希望得到一个比率,这也便于我们得到一个标准的梯度检验的要求。

为什么这样说呢?其实我们可以这样想,假设刚开始的迭代,参数的梯度很大,而随着不断迭代直至收敛,参数的梯度逐渐趋近于0,即越来越小,这个过程中,分子(欧氏距离)是跟梯度的值有关的,随着迭代次数的增加,也会减小。那在迭代过程中,我们只利用分子就没有一个固定标准去判断梯度检验的效果,而加上一个分母,将梯度的平方和考虑进去,大值比大值,小值比小值,我们就可以得到一个比率,同样也可以得到一个确定的标准去衡量梯度检验的效果。

算法

  1. 初始化神经网络的所有矩阵参数(可以使用随机初始化或其它非0的初始化方法)
  2. 把所有层的W,B都转化成向量,按顺序存放在$$\theta$$中
  3. 随机设置X值,最好是归一化之后的值,在[0,1]之间
  4. 做一次前向计算,再紧接着做一次反向计算,得到各参数的梯度$$d\theta_{real}$$
  5. 把得到的梯度$$d\theta_{real}$$变化成向量形式,其尺寸应该和第2步中的$$\theta$$相同,且一一对应(W对应dW, B对应dB)
  6. 对2中的$$\theta$$向量中的每一个值,做一次双边逼近,得到$$d\theta_{approx}$$
  7. 比较$$d\theta{real}$$和$$d\theta{approx}$$的值,通过计算两个向量之间的欧式距离:

$$
diff = {\parallel d\theta{real} - d\theta{approx}\parallel2 \over \parallel d\theta{approx}\parallel2 + \parallel d\theta{real}\parallel_2}
$$

结果判断:

  1. diff > 1e^{-2}
    梯度计算肯定出了问题。

  2. 1e^{-2} > diff > 1e^{-4}
    可能有问题了,需要检查。

  3. 1e^{-4} \gt diff \gt 1e^{-7}
    不光滑的激励函数来说时可以接受的,但是如果使用平滑的激励函数如 tanh nonlinearities and softmax,这个结果还是太高了。

  4. 1e^{-7} \gt diff
    可以喝杯茶庆祝下。

另外要注意的是,随着网络深度的增加会使得误差积累,如果用了10层的网络,得到的相对误差为1e-2那么这个结果也是可以接受的。

注意事项

  1. 首先,不要使用梯度检验去训练,即不要使用梯度检验方法去计算梯度,因为这样做太慢了,在训练过程中,我们还是使用backprop去计算参数梯度,而使用梯度检验去调试,去检验backprop的过程是否准确。
  2. 其次,如果我们在使用梯度检验过程中发现backprop过程出现了问题,就需要对所有的参数进行计算,以判断造成计算偏差的来源在哪里,它可能是在求解b出现问题,也可能是在求解某一层的W出现问题,梯度检验可以帮助我们确定发生问题的范围,以帮助我们调试。
  3. 别忘了正则化。如果我们添加了二范数正则化,在使用backprop计算参数梯度时,不要忘记梯度的形式已经发生了变化,要记得加上正则化部分,同理,在进行梯度检验时,也要记得目标函数J的形式已经发生了变化。
  4. 注意,如果我们使用了drop-out正则化,梯度检验就不可用了。为什么呢?因为我们知道drop-out是按照一定的保留概率随机保留一些节点,因为它的随机性,目标函数J的形式变得非常不明确,这时我们便无法再用梯度检验去检验backprop。如果非要使用drop-out且又想检验backprop,我们可以先将保留概率设为1,即保留全部节点,然后用梯度检验来检验backprop过程,如果没有问题,我们再改变保留概率的值来应用drop-out。
  5. 最后,介绍一种特别少见的情况。在刚开始初始化W和b时,W和b的值都还很小,这时backprop过程没有问题,但随着迭代过程的进行,W和b的值变得越来越大时,backprop过程可能会出现问题,且可能梯度差距越来越大。要避免这种情况,我们需要多进行几次梯度检验,比如在刚开始初始化权重时进行一次检验,在迭代一段时间之后,再使用梯度检验去验证backprop过程。

代码位置

ch12, Level2