写在前面
本博客是作者在学习机器学习基础时写下的总结,学习的资源为网易云课堂上吴恩达的机器学习课程,参考资料主要为stanford cs229课程的英文讲义,有兴趣的读者可以去网上下载原版的英文讲义来看。此外,由于本人为初学者,对知识点理解有限,因此文中有任何错误非常欢迎大家指出。最后,本文涉及的所有代码的完整版均会上传到github,欢迎大家交流。
1. 欠拟合与过拟合
在线性回归中,我们经常会用多项式来拟合一些非线性的数据,如下图:
当使用一次函数,也就是直线进行拟合时,假设函数为:
h
θ
(
x
)
=
θ
0
+
θ
1
x
h_\theta(x) = \theta_0+\theta_1x
hθ(x)=θ0+θ1x
得到的结果如下图所示:
由于数据有趋于平缓的趋势,而拟合直线斜率始终保持不变,因此这种情况称为欠拟合。
当使用二次多项式进行拟合时,即假设函数为:
h
θ
(
x
)
=
θ
0
+
θ
1
x
1
+
θ
2
x
2
h_\theta(x) = \theta_0+\theta_1x_1+\theta_2x^2
hθ(x)=θ0+θ1x1+θ2x2
得到的结果如下图所示:
从图中可以看出,利用二次多项式进行拟合的效果比直线进行拟合的效果好得多。
下面用六次多项式进行拟合,假设函数为:
h
θ
(
x
)
=
θ
0
+
θ
1
x
1
+
θ
2
x
2
+
θ
3
x
3
+
θ
4
x
4
+
+
θ
5
x
5
+
θ
6
x
6
h_\theta(x) = \theta_0+\theta_1x_1+\theta_2x^2+\theta_3x^3+\theta_4x^4++\theta_5x^5+\theta_6x^6
hθ(x)=θ0+θ1x1+θ2x2+θ3x3+θ4x4++θ5x5+θ6x6
得到的结果如下:
可以看到,虽然拟合曲线很好的拟合了所有的数据点,但该模型的泛化能力很差,也就是说该模型对除了训练数据以外的数据不具有很好的性能。我们称这种情况为过拟合。
过拟合和欠拟合是机器学习中一个很常见的现象,下面将介绍如何使用正则化来解决过拟合和欠拟合的问题。
2. 正则化
对于高次多项式拟合(以四次多项式为例),有假设函数为:
h
θ
(
x
)
=
θ
0
+
θ
1
x
1
+
θ
2
x
2
+
θ
3
x
3
+
θ
4
x
4
h_\theta(x) = \theta_0+\theta_1x_1+\theta_2x^2+\theta_3x^3+\theta_4x^4
hθ(x)=θ0+θ1x1+θ2x2+θ3x3+θ4x4
我们的优化目标是:
min
θ
1
2
m
∑
i
=
1
m
(
h
θ
(
x
(
i
)
)
−
y
(
i
)
)
2
\min_\theta \frac{1}{2m}\sum_{i=1}^m(h_\theta(x^{(i)})-y^{(i)})^2
θmin2m1i=1∑m(hθ(x(i))−y(i))2
为了防止过拟合,我们对高次项系数
θ
3
、
θ
4
\theta_3、\theta_4
θ3、θ4进行惩罚,具体操作是在上述成本函数中加上
θ
3
、
θ
4
\theta_3、\theta_4
θ3、θ4项:
min
θ
1
2
m
∑
i
=
1
m
(
h
θ
(
x
(
i
)
)
−
y
(
i
)
)
2
+
1000
θ
3
+
1000
θ
4
\min_\theta \frac{1}{2m}\sum_{i=1}^m(h_\theta(x^{(i)})-y^{(i)})^2+1000\theta_3+1000\theta_4
θmin2m1i=1∑m(hθ(x(i))−y(i))2+1000θ3+1000θ4
这里的1000是为了说明惩罚项的作用随意给的,这样使得模型在减少成本函数的时候必须使
θ
3
\theta_3
θ3和
θ
4
\theta_4
θ4很小才行,因此
θ
3
\theta_3
θ3和
θ
4
\theta_4
θ4的作用会因此大大衰减,从而减小了过拟合的可能性。
下面给出加上正则化之后的成本函数:
J
(
θ
)
=
1
2
m
[
∑
i
=
1
m
(
h
θ
(
x
(
i
)
−
y
(
i
)
)
2
+
λ
∑
j
=
1
n
θ
j
2
]
J(\theta) = \frac{1}{2m} \left[ \sum_{i=1}^m(h_\theta(x^{(i)}-y^{(i)})^2+\lambda\sum_{j=1}^n\theta_j^2 \right]
J(θ)=2m1[i=1∑m(hθ(x(i)−y(i))2+λj=1∑nθj2]
其中,
λ
∑
j
=
1
n
θ
j
2
\displaystyle \lambda\sum_{j=1}^n\theta_j^2
λj=1∑nθj2为正则化项。
λ
\lambda
λ的大小决定了正则化的效果,当
λ
=
0
\lambda=0
λ=0时,相当于正则项不起作用,因此结果还是会发生过拟合;当
λ
\lambda
λ大小合适时,我们会得到比较好的拟合效果;而当
λ
\lambda
λ太大时,会导致拟合效果变差,甚至导致欠拟合,因此选取合适的
λ
\lambda
λ才能使得正则化能有较好的效果。
此外,通常情况下我们不会对常数项进行正则化,因此上述正则项的求和是从1到n而不是0到n。
加上正则项之后成本函数的偏导数为:
∂
∂
θ
0
J
(
θ
)
=
1
m
∑
i
=
1
m
(
h
θ
(
x
(
i
)
−
y
(
i
)
)
x
0
(
i
)
∂
∂
θ
j
J
(
θ
)
=
(
1
m
∑
i
=
1
m
(
h
θ
(
x
(
i
)
−
y
(
i
)
)
x
j
(
i
)
)
+
λ
m
θ
j
f
o
r
j
>
0
\frac{\partial}{\partial\theta_0} J(\theta)= \frac{1}{m}\sum_{i=1}^m(h_\theta(x^{(i)}-y^{(i)})x_0^{(i)} \\\frac{\partial}{\partial\theta_j} J(\theta)= \left( \frac{1}{m}\sum_{i=1}^m(h_\theta(x^{(i)}-y^{(i)})x_j^{(i)}\right)+\frac{\lambda}{m}\theta_j\qquad for\quad j>0
∂θ0∂J(θ)=m1i=1∑m(hθ(x(i)−y(i))x0(i)∂θj∂J(θ)=(m1i=1∑m(hθ(x(i)−y(i))xj(i))+mλθjforj>0
下图蓝色线展示了加入正则项之后的拟合效果:
可以看到,正则化减轻了过拟合的程度。
对于正则项
λ
m
θ
j
\frac{\lambda}{m}\theta_j
mλθj,
λ
\lambda
λ的大小决定了正则化的效果,当
λ
\lambda
λ过小时,正则化不起作用,当
λ
\lambda
λ过大时,可能会导致欠拟合,如下图:
其中蓝色线为
λ
\lambda
λ过大时出现的欠拟合情况,进一步增加
λ
\lambda
λ的值,有
由于常数项
θ
\theta
θ没有进行正则化,因此当
λ
\lambda
λ过大时,所有的高次项都不起作用,引起最终会趋于一条平行于x轴的直线。
注:上述线性回归的代码在github,这里不再列出,感兴趣的朋友可以看一看。
3. 应用到逻辑回归中
和线性回归类似,逻辑回归同样存在过拟合的现象。实际上,过拟合现象在机器学习中广泛存在。
考虑下面一个简单的二元分类问题:
有假设函数
h
θ
(
x
)
=
g
(
θ
T
x
)
h_\theta(x) = g(\theta^Tx)
hθ(x)=g(θTx)
其中,
g
(
z
)
=
1
1
+
e
−
z
g(z) =\displaystyle\frac{1}{1+e^{-z}}
g(z)=1+e−z1为Sigmoid函数。
当利用线性函数进行回归时,有
x
=
(
1
,
x
0
,
x
1
)
x = (1, x_0, x_1)
x=(1,x0,x1)
当利用高次曲线进行回归时, 有
x
=
(
1
,
x
0
,
x
1
,
x
0
2
,
x
0
x
1
,
x
1
2
.
.
.
)
x = (1, x_0, x_1, x_0^2, x_0x_1, x_1^2...)
x=(1,x0,x1,x02,x0x1,x12...)
利用线性函数进行分类的结果如下图:
显然,线性函数达不到我们想要的结果。下面利用二次曲线进行拟合:
当阶次进一步提高时,拟合的曲线对训练集中的数据拟合度越来越高,但随着阶次的提高,将会出现过拟合现象如下:
该图是拟合曲线阶次n=6时的拟合结果,可以看到,这个时候过拟合现象已经非常严重了。对于训练集以外的数据,该模型不具有良好的性能。
下面在逻辑回归的成本函数和偏导数中加入正则项:
成本函数:
J
(
θ
)
=
1
m
∑
i
=
1
m
[
−
y
(
i
)
log
(
h
θ
(
x
(
i
)
)
)
−
(
1
−
y
(
i
)
)
log
(
1
−
h
θ
(
x
(
i
)
)
)
]
+
λ
2
m
∑
j
=
1
n
θ
j
2
J(\theta) = \frac{1}{m}\sum_{i=1}^m \left[ -y^{(i)}\log(h_\theta(x^{(i)}))-(1-y^{(i)})\log (1-h_\theta(x^{(i)})) \right]+\frac{\lambda}{2m}\sum_{j=1}^n\theta_j^2
J(θ)=m1i=1∑m[−y(i)log(hθ(x(i)))−(1−y(i))log(1−hθ(x(i)))]+2mλj=1∑nθj2
偏导数:
∂
J
(
θ
)
∂
θ
0
=
1
m
∑
i
=
1
m
(
h
θ
(
x
(
i
)
)
−
y
(
i
)
)
x
0
(
i
)
\frac{\partial{J(\theta)}}{\partial{\theta_0}} = \frac{1}{m}\sum_{i=1}^m(h_\theta(x^{(i)})-y^{(i)})x_0^{(i)}
∂θ0∂J(θ)=m1i=1∑m(hθ(x(i))−y(i))x0(i)
∂
J
(
θ
)
∂
θ
j
=
[
1
m
∑
i
=
1
m
(
h
θ
(
x
(
i
)
)
−
y
(
i
)
)
x
j
(
i
)
]
+
λ
m
θ
j
f
o
r
j
>
0
\frac{\partial{J(\theta)}}{\partial{\theta_j}} = \left[\frac{1}{m}\sum_{i=1}^m(h_\theta(x^{(i)})-y^{(i)})x_j^{(i)}\right] +\frac{\lambda}{m}\theta_j\qquad for\quad j > 0
∂θj∂J(θ)=[m1i=1∑m(hθ(x(i))−y(i))xj(i)]+mλθjforj>0
加入正则项后拟合的结果如下:
同样的,当正则化的系数
λ
\lambda
λ过大时,会导致欠拟合:
从上图中可以看出,由于发生了欠拟合,因此拟合曲线不能很好的将两组数据分离开来。
下面是逻辑回归部分的完整代码,数据集和线性回归的代码在github:
import numpy as np
import matplotlib.pyplot as plt
class logisRegression(object):
def __init__(self, data, alpha, lam, degree, iter):
self.data = data
self.alpha = alpha # learning rate
self.lam = lam # regularizatoin rate
self.degree = degree # degree of fitting curve
self.iter = iter # iterations
def mapFeature(self, data):
self.trainX = np.ones((data.shape[0], 1))
for i in range(self.degree): # 0..5 + 1
for j in range(i+2): # 0 0..1 ... 0..5
temp = np.power(data[:, 0], i+1-j)*np.power(data[:, 1], j)
self.trainX = np.c_[self.trainX, temp]
self.trainY = data[:, 2].reshape(data.shape[0], 1)
self.theta = np.ones((self.trainX.shape[1], 1))
return self.trainX, self.trainY
def sigmoid(self, x):
z = 1/(1+np.exp(-x))
return z
def update(self):
# Partial derivative of the cost function
self.cost = (1/self.data.shape[0])*(self.sigmoid(self.trainX.dot(self.theta))\
-self.trainY).T.dot(self.trainX).T
# Regularization
self.cost[1:] = self.cost[1:] + (self.lam/self.data.shape[0])*self.theta[1:]
# Update theta
self.theta = self.theta - self.alpha * self.cost
def regression(self):
self.mapFeature(self.data)
for i in range(self.iter):
self.update()
return self.theta
def plotFig(data, X, Y, Z):
a = data[np.where(data[:, 2] == 1)]
b = data[np.where(data[:, 2] == 0)]
plt.subplots(1, 1, figsize=(8, 5))
plt.plot(a[:, 0], a[:, 1], 'x') # group A
plt.plot(b[:, 0], b[:, 1], 'x') # group B
plt.contour(X, Y, Z, [0.5], Width=0.5, alpha=0.4) # decision boundary
plt.xlabel(r'$x_1$', size=16)
plt.ylabel(r'$x_2$', size=16)
plt.tick_params(labelsize=12)
plt.title("Logistic regression", size=18)
plt.show()
def generateTestData():
a = np.linspace(-1, 1.2, 100)
b = np.linspace(-1, 1.2, 100)
X = np.meshgrid(a, b)
temp = np.ones((X[0].size, 1))
testData = np.c_[X[0].reshape(X[0].size, 1), X[1].reshape(X[0].size, 1), temp]
return testData, X
if __name__ == "__main__":
# data
trainData = np.loadtxt(open("ex2data2.txt", "rb"), delimiter=",")
# train
f = logisRegression(trainData, 0.5, 0.2, 8, 300000) # data, alpha, lam, degree, iter
theta = f.regression()
# test
[testData, X] = generateTestData()
[testX, testY] = f.mapFeature(testData)
testZ = f.sigmoid(testX.dot(theta)).reshape(X[0].shape)
# plot
plotFig(trainData, X[0], X[1], testZ)