总所周知,回归是机器学习的入门,而对于这篇文章,我也是下了很大的功夫。对于最基础的线性回归(也就是),这里我就不再过多叙述了,并且在该文章里面涉及到回归基础的东西我也不再过多啰嗦,如果想再温故一遍,大家可以看看我的文章线性回归那一篇。这里咱们主要讲讲用更好的办法处理回归问题。首先我会和大家分享线性基底函数模型,再学会如何解决过拟合的问题,最后我们再用别的模型(或者是说别的函数)来更好的处理回归问题。这些知识都是我处理总结所得,内容较长,希望有所帮助。(代码如有不理解之处,可私信与我,如不能及时回复,还望海涵)
目录
一、线性基底函数模型
这里我们思考一组年龄X和身高T的数据,假设我们有16个人的数据,数据表示如下:
,
,n表示人数。
np.random.seed(seed=1) #固定随机数
X_n = 16 #16个人
X = 5 + 25 * np.random.rand(X_n) #年龄
Z = [170,108,0.2]
T = Z[0] - Z[1] * np.exp(-Z[2] * X) + 4 * np.random.randn(X_n) #身高
咱们用线性回归(y = w0 * X + W1)处理该数据。如下:
它的均方误差=49.03。还不错,但是对于这个结果我们还是不够满意,相比直线,曲线对数据的拟合效果会更好。想必使用曲线进行预测的误差也会更小。而表示曲线的模型有很多种,这里我们就介绍一种通用性高的线性基底函数模型。所谓基底函数,就是作为基础的函数。线性基底函数的思路是,把线性回归模型()中的X替换为基底函数
,以创建各种各样的函数,原来的线性回归模型就变成了
。
首先我们得考虑选择什么样的函数作为基底函数。这里我们看一下以高斯函数(高斯函数也是我们常用的基底函数)作为基底函数的线性函数模型。
高斯函数表示如下:
高斯函数的中心位置是,s是用于调用函数取值范围,它们都是由设计者决定的参数。
当u=2,s=2时,它的图像如下所示:
在这里我们先用4个高斯函数作为基底函数,在5岁到30岁的年龄范围内以相等间隔配置。
我们先来创造高斯函数:
def gauss(x,mu,s):
return np.exp(- (x - mu) ** 2/ (2 * s ** 2) )
再把原来的线性方程变成
def gauss_func(x,w,m): #x指的是我们的输入变量,年龄,m表示的高斯函数的个数,w自然就是参数W
y = np.zeros_like(x)
#注意这里一定是要用数组的形式(不能直接y=0),不然后面画图以及计算都会出错
mu = np.linspace(5,30,m)
s = mu[1] - mu[0]
for i in range(m):
y += w[i] * gauss(x,mu[i],s)
y = w[m] + y
return y
接着我们求出W带入到上面的函数gauss_func就可以得到我们的线性基底函数了。下面是求W:
def fit_gauss_func(x,m,t): #x,m与上述一样,t指的是我们的目标变量,身高
phi = np.ones((x.shape[0],m+1))
#这里一定记住是使用ones而不是zeros,不然后面计算会出错,而后面多加一列是因为线性公式的最后一个w不乘以任何的x
mu = np.linspace(5, 30, m)
s = mu[1] - mu[0]
for j in range(m):
phi[:,j] = gauss(x,mu[j],s)
phi_T = np.transpose(phi)
print(phi)
b = np.linalg.inv(phi_T.dot(phi))
c = b.dot(phi_T)
w = c.dot(t)
return w
这里我求解W用的并不是我们梯度下降,而是
,
至于这个公式怎么来的,我在这里也不过多叙述,只简单的说一下。它是由损失函数J对W0,W1.....求偏导,全部等于0后得出来的等式,不同于梯度下降得出的结果是近似解,这个公式得出的结果是严密的解,这样的解我们称为解析解。
除此之外呢,我们还得求出我们的误差以判断效果是否比原线性函数好,于是使用误差函数:
def mse_gauss_func(x,w,m,t): #x,w,m,t含义与上述一样
y = gauss_func(x,w,m)
mse = np.mean((y - t) ** 2)
return mse
最后得出均方误差=15.83。并且得图如下:
结果很明显,"会拐弯"的线性基底函数得出的结果要比直接使用线性函数效果要好。
但还有一个问题,如何决定基底函数的数量M呢?只要M足够大,就能很好地拟合如何数据吗?下面我们看看M = 2,4,7,9时的效果:
随着M的增大,均方误差J确实在逐渐变小,但是高斯基底函数却变得越来越曲折?!这样就不能很好地对新的输入进行预测了。这种现象称为过拟合。而我们下一个任务就是解决过拟合问题。
二、解决过拟合问题
如上所知,均方误差会随着M的增大而不断的减小,所以无法根据均方误差找到最优的M值。因此,我们想要回归本源,从真正的目标——对新数据的预测精度的角度考虑这个问题。
首先我们将手头的数据X和T分成两部分,比如将1/4的数据作为测试数据,将剩余的3/4作为训练数据。然后,只使用训练数据对模型的参数W进行最优化,接着,使用通过这种方式确定的W计算测试数据的均方误差,并将其作为M的评价标准。我们称这样的方法为留出验证。但是这种方式也是有缺陷的,它太依赖于测试数据的选择。为了改进这种方法,后面就提出了交叉验证。交叉验证是一种对不同分法的误差取平均值的方法,我们也可以根据数据分割的份数称之为K折交叉验证。
将输入数据和目标数据分割为K份,将其中一份作为测试数据,其余的作为训练数据。使用训练数据对模型参数进行最优化,并计算测试数据的均方误差。然后更换测试数据,重复执行K次这样的操作。最后取K个均方误差的平均值作为模型的评估值。
下面是我用代码对K折交叉验证的实现:
def kfold_gauss_func(x,m,t,k): #x,m,t含义与上面代码一样,K表示将数据切割的份数
n = x.shape[0]
mse_test = np.zeros(k)
for i in range(k):
x_train = x[np.fmod(range(n),k) != i] #np.fmod(n,k)函数的作用是输出n被k除时的余数
t_train = t[np.fmod(range(n),k) != i]
x_test = x[np.fmod(range(n),k) == i]
t_test = t[np.fmod(range(n),k) == i]
W = fit_gauss_func(x_train,m,t_train)
mse_test[i] = mse_gauss_func(x_test,W,m,t_test)
return mse_test #只用返回测试的均方误差即可
接下来,我们通过K折交叉验证计算,当分割数K=16时,M为从2到7时的平均值,并绘制图形:
M = range(2,8)
K = 16
MSE_test = np.zeros((K,len(M)))
for i in range(len(M)):
MSE_test[:,i] = kfold_gauss_func(X,M[i],T,K)
mean_MSE_test = np.mean(MSE_test,axis=0)
plt.plot(M,mean_MSE_test,'b',marker='o',linestyle='-',label='test_mse')
plt.legend(loc='upper left')
plt.grid(True)
plt.show()
图像如下所示:
由图易得,当M=3的时候,测试数据的均方误差是最小的。咱们啊,就通过这样的方式就可以找到最优的M值,从而避免了过拟合的问题。
三、新模型的运用
引入线性基底函数模型后,曲线对数据的误差得到了大幅度的改善,并且我们同过K折交叉验证避免了过拟合。不过,对于年龄和身高这对关系还存在一个问题,我们人一般在25岁以后呢,身高基本不会再由改动,所以为了符合这一个实际规律。我们最好的办法就是创造一个符合这条规律的函数模型。使得身高随着年龄的增长而缓慢增加,并且到一定年龄之后收敛。
针对这样的规律,我们有这样一个函数:
随着x的增大,逐渐接近0。最终,只有第1项的
有值。换言之,随着x的增大,y逐渐逼近
。因为这个函数的特点,我们可以用它来代替我们原本的线性函数
(其实对于许多的实际问题上面,我们都有很多其它的函数代替线性函数以达到更好的效果,所以说,我们这一行很依赖数学啊),最后求参数W的过程也与原来的方法一致,可以用梯度下降,也可以用我上面提到的解析解的方法,这里尝试使用实现了前者的梯度下降的库来求解。
这里我们使用Python的scipy.optimize库中包含的minimize函数求解W。使用这个函数,只需要提供求解最小值的函数和参数的初始值,而无须提供函数的导数,就能输出参数的极小值解,非常方便。
def model_A(x,w):
y = w[0] - w[1] * np.exp(-w[2] * x)
return y
def mse_model_A(w,x,t):
y = model_A(x,w)
mse = np.mean((y - t) ** 2)
return mse
def fit_model_A(w_init,x,t):
res1 = minimize(mse_model_A,w_init,args=(x,t),method="powell")
return res1.x
#这里千万注意啊,fit_model_A和mse_model_A二者的形参顺序必须一致。返回的res1.x的就是我们要的W。
最后我们通过图像来看看函数的效果:
结果均方误差=14.91,图像的曲线也非常的符合我们的期望。
我们在解决实际的回归问题中,为了使得效果更好,我们会往往尝试使用多个不同的函数模型,而判断函数模型的好坏就可以使用我们上面学习的交叉验证的方法。
好的,非常,谢谢大家能够耐心的看完,希望我分享的内容能够对大家有所帮助,下面就是对应内容的源代码。
四、源码
(1)——线性基底函数模型
np.random.seed(seed=1)
X_min = 4
X_max = 30
X_n = 16
X = 5 + 25 * np.random.rand(X_n)
Z = [170,108,0.2]
T = Z[0] - Z[1] * np.exp(-Z[2] * X) + 4 * np.random.randn(X_n)
y = 1.6 * X +135.9
def gauss(x,mu,s):
return np.exp(- (x - mu) ** 2/ (2 * s ** 2) )
def gauss_func(x,w,m):
y = np.zeros_like(x)
mu = np.linspace(5,30,m)
s = mu[1] - mu[0]
for i in range(m):
y += w[i] * gauss(x,mu[i],s)
y = w[m] + y
return y
def mse_gauss_func(x,w,m,t):
y = gauss_func(x,w,m)
mse = np.mean((y - t) ** 2)
return mse
def fit_gauss_func(x,m,t):
phi = np.ones((x.shape[0],m+1))
mu = np.linspace(5, 30, m)
s = mu[1] - mu[0]
for j in range(m):
phi[:,j] = gauss(x,mu[j],s)
phi_T = np.transpose(phi)
b = np.linalg.inv(phi_T.dot(phi))
c = b.dot(phi_T)
w = c.dot(t)
return w
M = 4
x = np.linspace(0,30,100)
W = fit_gauss_func(X,M,T)
y = gauss_func(x,W,M)
plt.plot(X,T,'b',marker='o',linestyle='None')
plt.grid(True)
plt.plot(x,y)
plt.show()
print(mse_gauss_func(X,W,M,T))
#M = [2,4,7,9]得到的图像
M = [2,4,7,9]
plt.figure(figsize=(10,2.5))
for i in range(4):
x = np.linspace(0,30,100)
W = fit_gauss_func(X,M[i],T)
y = gauss_func(x,W,M[i])
plt.subplot(1,4,i+1)
plt.plot(X, T, 'b', marker='o', linestyle='None')
plt.grid(True)
plt.ylim(130,180)
plt.plot(x, y)
plt.title("M={0} J={1}".format(M[i],np.round(mse_gauss_func(X,W,M[i],T),2)))
plt.show()
(2)——解决过拟合问题
np.random.seed(seed=1)
X_min = 4
X_max = 30
X_n = 16
X = 5 + 25 * np.random.rand(X_n)
Z = [170,108,0.2]
T = Z[0] - Z[1] * np.exp(-Z[2] * X) + 4 * np.random.randn(X_n)
y = 1.6 * X +135.9
def gauss(x,mu,s):
return np.exp(- (x - mu) ** 2/ (2 * s ** 2) )
def gauss_func(x,w,m):
y = np.zeros_like(x)
mu = np.linspace(5,30,m)
s = mu[1] - mu[0]
for i in range(m):
y += w[i] * gauss(x,mu[i],s)
y = w[m] + y
return y
def mse_gauss_func(x,w,m,t):
y = gauss_func(x,w,m)
mse = np.mean((y - t) ** 2)
return mse
def fit_gauss_func(x,m,t):
phi = np.ones((x.shape[0],m+1))
mu = np.linspace(5, 30, m)
s = mu[1] - mu[0]
for j in range(m):
phi[:,j] = gauss(x,mu[j],s)
phi_T = np.transpose(phi)
b = np.linalg.inv(phi_T.dot(phi))
c = b.dot(phi_T)
w = c.dot(t)
return w
def kfold_gauss_func(x,m,t,k):
n = x.shape[0]
mse_test = np.zeros(k)
for i in range(k):
x_train = x[np.fmod(range(n),k) != i]
t_train = t[np.fmod(range(n),k) != i]
x_test = x[np.fmod(range(n),k) == i]
t_test = t[np.fmod(range(n),k) == i]
W = fit_gauss_func(x_train,m,t_train)
mse_test[i] = mse_gauss_func(x_test,W,m,t_test)
return mse_test
M = range(2,8)
K = 16
MSE_test = np.zeros((K,len(M)))
for i in range(len(M)):
MSE_test[:,i] = kfold_gauss_func(X,M[i],T,K)
mean_MSE_test = np.mean(MSE_test,axis=0)
plt.plot(M,mean_MSE_test,'b',marker='o',linestyle='-',label='test_mse')
plt.legend(loc='upper left')
plt.grid(True)
plt.show()
(3)——新模型的运用
np.random.seed(seed=1)
X_min = 4
X_max = 30
X_n = 16
X = 5 + 25 * np.random.rand(X_n)
Z = [170,108,0.2]
T = Z[0] - Z[1] * np.exp(-Z[2] * X) + 4 * np.random.randn(X_n)
def model_A(x,w):
y = w[0] - w[1] * np.exp(-w[2] * x)
return y
def mse_model_A(w,x,t):
y = model_A(x,w)
mse = np.mean((y - t) ** 2)
return mse
def fit_model_A(w_init,x,t):
res1 = minimize(mse_model_A,w_init,args=(x,t),method="powell")
return res1.x
W_init = [100,0,0]
W = fit_model_A(W_init,X,T)
xb = np.linspace(0,30,100)
y = model_A(xb,W)
plt.plot(X,T,'b',marker='o',linestyle='None')
plt.plot(xb,y,'black')
plt.show()
print(mse_model_A(W,X,T))