朴素的想法

从过拟合的现象分析,是因为神经网络的权重矩阵参数过度地学习,即针对训练集,其损失函数值已经逼近了最小值。我们用熟悉的等高线图来解释,如图16-11所示。

L2正则 - 图1

假设只有两个参数需要学习,那么这两个参数的损失函数就构成了上面的等高线图。由于样本数据量比较小(这是造成过拟合的原因之一),所以神经网络在训练过程中沿着箭头方向不断向最优解靠近,最终达到了过拟合的状态。也就是说在这个等高线图中的最优解,实际是针对有限的样本数据的最优解,而不是针对这个特点问题的最优解。

由此会产生一个朴素的想法:如果我们以某个处于中间位置等高线上(比如那条红色的等高线)为目标的话,是不是就可以得到比较好的效果呢?如何科学地找到这条等高线呢?

基本数学知识

范数

回忆一下范数的基本概念:

Lp = \lVert x \rVert_p = ({\sum^n{i=1}\lvert x_i \rvert{1/p} \tag{1}

范数包含向量范数和矩阵范数,我们只关心向量范数。我们用具体的数值来理解范数。假设有一个向量a:

$$
a=[1,-2,0,-4]
$$

L_0=3 \tag{非0元素数}

L1 = \sum^3{i=0}\lvert x_i \rvert = 1+2+0+4=7 \tag{绝对值求和}

L2 = \sqrt[2]{\sum^3{i=0}\lvert x_i \rvert^2} =\sqrt[2]{21}=4.5826 \tag{平方和求方根}

L_{\infty}=4 \tag{最大值的绝对值}

注意p可以是小数,比如0.5:

$$
L_{0.5}=19.7052
$$

一个经典的关于P范数的变化如图16-12所示。

L2正则 - 图2

我们只关心L1和L2范数:

  • L1范数是个菱形体,在平面上是一个菱形
  • L2范数是个球体,在平面上是一个圆

高斯分布

f(x)={1 \over \sigma\sqrt{2 \pi}} exp{- {(x-\mu)^2} \over 2\sigma^2} \tag{2}

L2正则化

假设:

  • W参数服从高斯分布,即:$$w_j \sim N(0,\tau^2)$$
  • Y服从高斯分布,即:$$y_i \sim N(w2)$$

贝叶斯最大后验估计:

argmax_wL(w) = ln \prod_i^n {1 \over \sigma\sqrt{2 \pi}}exp(-(\frac{y_i-w2/2) \cdot \prod_j2/2)}

=-\frac{1}{2\sigman(y_i-w2-\frac{1}{2\taum{w_j^2}-n\ln\sigma\sqrt{2\pi}-m\ln \tau\sqrt{2\pi} \tag{3}

因为\sigma、b、n、\pi、m等都是常数,所以损失函数J(w)的最小值可以简化为:

argmin_wJ(w) = \sum_iTx_i)m{w_j^2} \tag{4}

看公式4,相当于是线性回归的均方差损失函数,再加上一个正则项(也称为惩罚项),共同构成损失函数。如果想求这个函数的最小值,则需要两者协调,并不是说分别求其最小值就能实现整体最小,因为它们具有共同的W项,当W比较大时,第一项比较小,第二项比较大,或者正好相反。所以它们是矛盾组合体。

为了简化问题便于理解,我们用两个参数w1,w2举例。对于公式4的第一项,我们用前面学习过损失函数的等高线图来解释。对于第二项,形式应该是一个圆形,因为圆的方程是r2+y^2。所以,结合两者,我们可以得到图16-13。

L2正则 - 图3

黄色的圆形,就是正则项所处的区域。这个区域的大小,是由参数\lambda所控制的,该值越大,黄色圆形区域越小,对w的惩罚力度越大(距离椭圆中心越远)。比如图16-13中分别标出了该值为0.7、0.8、0.9的情况。

还以图16-13为例,当\lambda为0.7时,L2正则区为图中所示最大的黄色区域,此区域与损失函数等高线图的交点有多个,比如图中的红、绿、蓝三个点,但由于红点距离椭圆中心最近,所以最后求得的权重值应该在红点的位置坐标上(w_1,w_2)

在回归里面,把具有L2项的回归叫“岭回归”(Ridge Regression),也叫它“权值衰减”(weight decay)。 weight decay还有一个好处,它使得目标函数变为凸函数,梯度下降法和L-BFGS都能收敛到全局最优解。

L2范数是指向量各元素的平方和然后求平方根。我们让L2范数的规则项最小,可以使得W的每个元素都很小,都接近于0,因为一般认为参数值小的模型比较简单,能适应不同的数据集,也在一定程度上避免了过拟合现象。可以设想一下对于一个线性回归方程,若参数很大,那么只要数据偏移一点点,就会对结果造成很大的影响;但如果参数足够小,数据偏移得多一点也不会对结果造成什么影响,专业一点的说法是“抗扰动能力强”。

关于bias偏置项的正则

上面的L2正则化没有约束偏置(biases)项。当然,通过修改正则化过程来正则化偏置会很容易,但根据经验,这样做往往不能较明显地改变结果,所以是否正则化偏置项仅仅是一个习惯问题。

值得注意的是,有一个较大的bias并不会使得神经元对它的输入像有大权重那样敏感,所以不用担心较大的偏置会使我们的网络学习到训练数据中的噪声。同时,允许大的偏置使我们的网络在性能上更为灵活,特别是较大的偏置使得神经元更容易饱和,这通常是我们期望的。由于这些原因,通常不对偏置做正则化。

损失函数的变化

假设是均方差损失函数:

J(w,b)=\frac{1}{2m}\sum{i=1}^m (z_i-y_i)^2 + {\lambda \over 2m}\sum{j=1}2} \tag{5}

如果是交叉熵损失函数:

J(w,b)= -\frac{1}{m} \sum{i=1}^m [y_i \ln a_i + (1-y_i) \ln (1-a_i)]+ \frac{\lambda}{2m}\sum{j=1}2} \tag{6}

在NeuralNet.py中的代码片段如下,计算公式5或公式6的第二项:

  1. for i in range(self.layer_count-1,-1,-1):
  2. layer = self.layer_list[i]
  3. if isinstance(layer, FcLayer):
  4. if regularName == RegularMethod.L2:
  5. regular_cost += np.sum(np.square(layer.weights.W))
  6. return regular_cost * self.params.lambd

如果是FC层,则取出W值的平方,再求和,最后乘以\lambda系数返回。

在计算Loss值时,用上面函数的返回值再除以样本数m,即下面代码中的train_y.shape[0],附加到原始的loss值之后即可。下述代码就是对公式5或6的实现。

  1. loss_train = self.lossFunc.CheckLoss(train_y, self.output)
  2. loss_train += regular_cost / train_y.shape[0]

反向传播的变化

由于正则项是在损失函数中,在正向计算中,并不涉及到它,所以正向计算公式不用变。但是在反向传播过程中,需要重新推导一下公式。

假设有一个两层的回归神经网络,其前向计算如下:

Z1 = W1 \cdot X + B1 \tag{5}

A1 = Sigmoid(Z1) \tag{6}

Z2 = W2 \cdot A1 + B2 \tag{7}

J(w,b)=\frac{1}{2m}[\sum{i=1}^m (z_i-y_i)^2 + \lambda\sum{j=1}2}] \tag{8}

从公式8求Z2的误差矩阵:

$$
dZ2 = \frac{dJ}{dZ2}=Z2-Y
$$

从公式8求W2的误差矩阵,因为有正则项存在,所以需要附加一项:

\begin{aligned} \frac{dJ}{dW2}&=\frac{dJ}{dZ2}\frac{dZ2}{dW2}+\frac{dJ}{dW2} \ =(Z2-Y)\cdot A1^T+\lambda \odot W2 \end{aligned} \tag{9}

公式8是W1,W2的总和,公式9对dJ/dW2求导时,由于是W12的关系,所以W1对W2求导的结果是0,所以公式9最后只剩下W2了。

B不受正则项的影响:

dB2=dZ2 \tag{10}

再继续反向传播到第一层网络:

dZ1 = W2^T \times dZ2 \odot A1 \odot (1-A1) \tag{11}

dW1= dZ1 \cdot X^T + \lambda \odot W1 \tag{12}

dB1= dZ1 \tag{13}

从上面的公式中可以看到,正则项在方向传播过程中,唯一影响的就是求W的梯度时,要增加一个\lambda \odot W,所以,我们可以修改FullConnectionLayer.py中的反向传播函数如下:

  1. def backward(self, delta_in, idx):
  2. dZ = delta_in
  3. m = self.x.shape[1]
  4. if self.regular == RegularMethod.L2:
  5. self.weights.dW = (np.dot(dZ, self.x.T) + self.lambd * self.weights.W) / m
  6. else:
  7. self.weights.dW = np.dot(dZ, self.x.T) / m
  8. # end if
  9. self.weights.dB = np.sum(dZ, axis=1, keepdims=True) / m
  10. delta_out = np.dot(self.weights.W.T, dZ)
  11. if len(self.input_shape) > 2:
  12. return delta_out.reshape(self.input_shape)
  13. else:
  14. return delta_out

当regular == RegularMethod.L2时,走一个特殊分支,完成正则项的惩罚机制。

运行结果

下面是主程序的运行代码:

  1. from Level0_OverFitNet import *
  2. if __name__ == '__main__':
  3. dr = LoadData()
  4. hp, num_hidden = SetParameters()
  5. hp.regular_name = RegularMethod.L2
  6. hp.regular_value = 0.01
  7. net = Model(dr, 1, num_hidden, 1, hp)
  8. ShowResult(net, dr, hp.toString())

运行后,将训练过程中的损失和准确率可视化出来,并将拟合后的曲线与训练数据做比较,如图16-14和16-15所示。

L2正则 - 图4

L2正则 - 图5

代码位置

ch16, Level2

keras实现

  1. model.add(Dense(64,
  2. activation='relu',
  3. kernel_regularizer=l2(0.01)))

参考资料

[1] http://charleshm.github.io/2016/03/Regularized-Regression/

[2] https://blog.csdn.net/red_stone1/article/details/80755144

[3] https://www.jianshu.com/p/c9bb6f89cfcc