5.7正则化
上次我们提到模型对训练数据过度的优化拟合,使模型能够很好的对训练数据进行拟合,但却对测试数据的预测或者分类效果很差,这种状态被称为过拟合。一般过度增加函数的次数会导致过拟合的情况。我们有哪些方法可以避免过拟合的情况呢?一般情况下:
- 增加全部训练数据的训练量
- 使用简单的模型
- 正则化
我们知道机器学习是从数据中学习的,所以足够的训练数据最重要。其次简单的模型也有助于防止过拟合的情况。我们现在需要着重介绍的是正则化的方法:
首先我们要理解使用正则化产生的效果。我们知道无论回归还是分类,参数θ是对模型的拟合产生影响的最主要因素,一般模型的过拟合就是由于参数的影响过大。而正则化的效果就是防止参数的影响过大,在训练时对参数施加的一些“惩罚”,让参数对模型的影响变小一点,从而达到防止过拟合的效果。
5.7.1回归的正则化
我们现在对这个新的目标函数进行最小化,这种方法称为正则化。正则项里的m表示正则化的对象参数的个数,θ0被称为偏置项,一般不对θ0正则化,所以j的取值是从1开始的。举个例子,假设预测函数的表达式为f(x)=θ0+θ1x+θ2x2,那么m=2就是表示正则化的对象参数为θ1和θ2。而λ是决定正则化影响程度的正的常数,这个值需要我们根据情况自己决定,后面会再提及。
只看表达式不容易理解正则化是如何防止过拟合的,我们可以结合图来理解:
我们首先把新组合成的目标函数拆开:
我们将这两个函数分别画出来后再加起来看看,不过参数过多的话就画不出图来了,所以假设f(x)=θ0+θ1x,而且为了方便理解,先不考虑λ,或令λ=1。在将回归的时候我们提到过这个目标函数C(θ)开口向上,所以我们假设一个开口向上的函数即可,R(θ)是一个标准的二次函数表达式1/2*θ12。则将两个函数的图形画出,如图5-7-2所示:
图5-7-2
新的目标函数是这两个函数之和E(θ)=C(θ)+R(θ),如何画出E(θ)的图形呢?我们这里只需要将C(θ)和R(θ)在横坐标θ1上各点的值相加映射到坐标轴上即可,如图5-7-3所示:
图5-7-3
本来目标函数在θ1=4.5处最小,加上正则项后,现在θ1=0.9处最小,我们可以发现θ1跟接近0了。这个就是正则化的效果,它可以防止参数变得过大,有助于参数接近最小的值。虽然我们只考虑了θ1,但是其它θj参数的情况也是类似的。参数的值变小了,那么意味着该参数的影响也会相应的变小。比如有这样的一个预测函数f(x)=θ0+θ1x+θ2x2。极端一点,假设θ2=0了,那么表达式就从二次变成了一次了。这正是通过减小不需要的参数的影响,将复杂的模型替换为简单的模型来防止过拟合的方式。之前提到的λ就是控制正则化惩罚强度的一个正的常数,是根据实际情况来决定的,如λ=0时相当于不使用正则化,反过来λ越大正则化的惩罚就越厉害。
5.7.2分类的正则化
分类的正则化与回归的正则化类似,就是在目标函数的基础上增加正则项。我们之前提到过逻辑回归的目标函数是对数似然函数:
我们只需要在这个目标函数的基础上增加正则项就可以了,但是对数似然函数是以最大化为目标,我们要把它变成和回归的目标函数一样处理最小化问题,所以前面加一个负号即可:
5.7.3 包含正则项表达式的微分
刚才我们把回归的目标函数分成C(θ)和R(θ)。这是新的目标函数的形式,我们要对它进行微分:E(θ)=C(θ)+R(θ)。由于是加法,所以我们对各部分进行偏微分:
首先对第一部分进行微分,具体微分步骤如下图5-7-4。
图5-7-4
所以,第一部分微分表达式如下:
先对第一个部分即原目标函数进行微分,微分具体步骤如图5-7-5:
图5-7-5
之前提到考虑的是最小化问题,所以在前面加上负号,所以将括号内的数进行反转:
刚才我们已经求过R(θ)的微分了,直接使用即可。与回归的正则化一样,θ0不参与正则化,所以要将参数的更新表达式“分段”:
这就是我们要求的逻辑回归正则化的参数更新表达式了。
我们这里讲的正则化方法其实叫L2正则化,除了L2正则化,还有一个叫L1正则化,L1正则化项是这样的:
虽然有两种正则化方法,但是我们可以根据实际情况来进行选择。L1正则化的特征是被判定为不需要的参数会变为0,从而减少变量的个数,而L2正则化不会把参数变为0。换句话说,L2正则化会抑制参数,使变量影响不会太大,而L1会直接去除不要的变量。
既然知道了正则化的表达式,我们就很好理解代码了,下面是应用正则化和没有使用正则化的对比:
import numpy as np
import matplotlib.pyplot as plt
# 真正的函数
def g(x):
return 0.1 * (x ** 3 + x ** 2 + x)
# 随意准备一些向真正的函数加入了一点噪声的训练数据
train_x = np.linspace(-2, 2, 8)
train_y = g(train_x) + np.random.randn(train_x.size) * 0.05
# 标准化
mu = train_x.mean()
sigma = train_x.std()
def standardize(x):
return (x - mu) / sigma
train_z = standardize(train_x)
# 创建训练数据的矩阵
def to_matrix(x):
return np.vstack([
np.ones(x.size),
x,
x ** 2,
x ** 3,
x ** 4,
x ** 5,
x ** 6,
x ** 7,
x ** 8,
x ** 9,
x ** 10
]).T
X = to_matrix(train_z)
# 参数初始化
theta = np.random.randn(X.shape[1])
# 预测函数
def f(x):
return np.dot(x, theta)
# 目标函数
def E(x, y):
return 0.5 * np.sum((y - f(x)) ** 2)
# 正则化常量
LAMBDA = 0.5
# 学习率
ETA = 1e-4
# 误差
diff = 1
# 重复学习(不应用正则化)
error = E(X, train_y)
while diff > 1e-6:
theta = theta - ETA * (np.dot(f(X) - train_y, X))
current_error = E(X, train_y)
diff = error - current_error
error = current_error
theta1 = theta
# 重复学习(应用正则化)
theta = np.random.randn(X.shape[1])
diff = 1
error = E(X, train_y)
while diff > 1e-6:
reg_term = LAMBDA * np.hstack([0, theta[1:]])
theta = theta - ETA * (np.dot(f(X) - train_y, X) + reg_term)
current_error = E(X, train_y)
diff = error - current_error
error = current_error
theta2 = theta
# 绘图确认
plt.plot(train_z, train_y, 'o')
z = standardize(np.linspace(-2, 2, 100))
theta = theta1 # 未应用正则化
plt.plot(z, f(to_matrix(z)), linestyle='dashed')
theta = theta2 # 应用正则化
plt.plot(z, f(to_matrix(z)))
plt.show()
运行结果如下:
截图和代码引自《白话机器学习的数学》