有监督学习:回归(进阶——跳过简单的线性回归)

发布于:2023-01-17 ⋅ 阅读:(451) ⋅ 点赞:(0)

  总所周知,回归是机器学习的入门,而对于这篇文章,我也是下了很大的功夫。对于最基础的线性回归(也就是y = W^{T}X),这里我就不再过多叙述了,并且在该文章里面涉及到回归基础的东西我也不再过多啰嗦,如果想再温故一遍,大家可以看看我的文章线性回归那一篇。这里咱们主要讲讲用更好的办法处理回归问题。首先我会和大家分享线性基底函数模型,再学会如何解决过拟合的问题,最后我们再用别的模型(或者是说别的函数)来更好的处理回归问题。这些知识都是我处理总结所得,内容较长,希望有所帮助。(代码如有不理解之处,可私信与我,如不能及时回复,还望海涵)

目录

一、线性基底函数模型

二、解决过拟合问题

三、新模型的运用

四、源码

  (1)——线性基底函数模型

   (2)——解决过拟合问题

  (3)——新模型的运用


一、线性基底函数模型

 这里我们思考一组年龄X和身高T的数据,假设我们有16个人的数据,数据表示如下:

X = [x0,x1,x2.....x_{n-1}]T = [t0,t1,t2......t_{n-1}],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。还不错,但是对于这个结果我们还是不够满意,相比直线,曲线对数据的拟合效果会更好。想必使用曲线进行预测的误差也会更小。而表示曲线的模型有很多种,这里我们就介绍一种通用性高的线性基底函数模型。所谓基底函数,就是作为基础的函数。线性基底函数的思路是,把线性回归模型(y = W^{T}X)中的X替换为基底函数\phi (X),以创建各种各样的函数,原来的线性回归模型就变成了y=W^{T}\phi (x)

  首先我们得考虑选择什么样的函数作为基底函数。这里我们看一下以高斯函数(高斯函数也是我们常用的基底函数)作为基底函数的线性函数模型。

  高斯函数表示如下:

                                               \phi (x) = exp{​{-\frac{(x-\mu )^{2}}{2s^{2}}}

高斯函数的中心位置是\mu,s是用于调用函数取值范围,它们都是由设计者决定的参数。 

当u=2,s=2时,它的图像如下所示:

在这里我们先用4个高斯函数作为基底函数,在5岁到30岁的年龄范围内以相等间隔配置。

我们先来创造高斯函数:

def gauss(x,mu,s):
    return np.exp(- (x - mu) ** 2/ (2 * s ** 2) )

再把原来的线性方程y = W^{T}X变成y=W^{T}\phi (x)

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用的并不是我们梯度下降,而是

                         W = (X^{T}X)^{-1}X^{T}T

至于这个公式怎么来的,我在这里也不过多叙述,只简单的说一下。它是由损失函数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岁以后呢,身高基本不会再由改动,所以为了符合这一个实际规律。我们最好的办法就是创造一个符合这条规律的函数模型。使得身高随着年龄的增长而缓慢增加,并且到一定年龄之后收敛。

  针对这样的规律,我们有这样一个函数:

                 y(x) = w_{0} - w_{1}exp(-w_{2}x)

  随着x的增大,exp(-w_{2}x)逐渐接近0。最终,只有第1项的w_{0}有值。换言之,随着x的增大,y逐渐逼近w_{0}。因为这个函数的特点,我们可以用它来代替我们原本的线性函数y=W^{T}X(其实对于许多的实际问题上面,我们都有很多其它的函数代替线性函数以达到更好的效果,所以说,我们这一行很依赖数学啊),最后求参数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))