机器学习实践之预测数值型数据--回归

   本文根据最近学习机器学习书籍网络文章的情况,特将一些学习心得做了总结,详情如下.如有不当之处,请各位大拿多多指点,在此谢过。


一、概述

1、相关概念      

1.1  回归

         “回归”一词的来历: 今天我们知道的  回归是由达尔文(Charles Darwin)的表弟Francis Galton发明的。Galton于1877年完成了第一次回归预测,目的是根据上一代豌豆种子(双亲)的尺寸来预测下一代豌豆种子(孩子)的尺寸。Galton在大量对象上应用了回归分析,甚至包括人的身高。他观察到,如果双亲的身高高于平均身高,他们的子代也倾向于高于平均身高但不及双亲高。子代的身高向着平均身高回退(回归)。

         提及回归一般是指线性回归(Linear Regression),至于非线性回归,本文先不做相关说明。在回归方程中,求回归系数的过程就是回归。

           和之前分类得到的离散型数据相比,这里的回归是为了处理连续型目标数据,所以,回归的目的也就是预测数值型目标变量值。

1.2  最小二乘法

        最小二乘法(又称最小平方法)是一种数学优化技术。它通过最小化误差的平方和寻找到数据的最佳匹配函数。在一元线性回归方程中,最小化乘法的原则是以“参差平方和最小”确定拟合直线的位置。

      我们应该怎样从一大堆数据里求出回归方程呢? 假定输入数据存放在矩阵 x 中,而回归系数存放在向量 w 中。那么对于给定的数据 X1,预测结果将会通过 Y = X1^T w 给出。现在的问题是,手里有一些 X 和对应的 y,怎样才能找到 w 呢?一个常用的方法就是找出使误差最小的 w 。这里的误差是指预测 y 值和真实 y 值之间的差值,使用该误差的简单累加将使得正差值和负差值相互抵消,所以我们采用平方误差(实际上就是我们通常所说的最小二乘法)。

平方误差可以写做(也就是回归中使用的 loss function):

     


     用矩阵表示:  。如果对 w 求导,得到 ,令其等于零,解出 w,如下:

            这里上方被标记的w是当前可以估计的最优解。

1.3 矩阵求逆

      在求回归方程中的回归系数时,其实就是在寻找使得平方误差最小的回归系数时,需要对矩阵求逆,,这个方程只在矩阵求逆时适用,但有时矩阵求逆可能不存在,所以必须在代码中对此做出准确判断。判断矩阵求逆是否可行的标准是:

        判断矩阵的行列式是否为0 ,若是则矩阵不可求逆;否则矩阵则可求逆。

 

2、回归开发流程

     收集数据:  采用任意方法收集数据。

     准备数据:  回归需要数值型数据,标称型数据将被转换为二值型数据。

      分析数据: 绘制出可视化的二维图将有助于对数据进行理解和分析,在采用缩减法求得新的回归系数之后,可以将新的拟合线绘制在图上进行比较分析。

      训练算法: 找到回归系数。

      测试算法:  使用R^2或者预测值与数据的拟合度,来分析模型的效果。

      使用算法:  使用回归,可以在给定输入时预测出一个数值,这是对分类算法的提升,因为这样可以预测连续型数据而不仅仅只预测离散型的分类标签。


3、 线性回归的相关特性

       优点: 结果易于理解, 计算上不复杂。

       缺点: 对非线性的拟合不够好。

      使用数据类型: 数值型和标称型数据。

二、   回归场景

        假如你想了解你男朋友跑车的功率大小,可能会采取以下公式:HorsePower = 0.0015 * annualSalary - 0.99 * hoursListeningToPublicRadio。

        这就是所谓的回归,其中的0.0015和-0.99就被称为回归系数(regression weights),当然求这些回归系数的过程就是回归。一旦我们得到这些回归系数,再给出输入,则做预测就非常容易了。具体做法是回归系数与输入值相乘,再将结果全部累加在一起,就得到我们的预测值了。当然,在这里回归系数是一个向量,输入也是一个向量,相关运算就是求二者的内积。

         扩展:其实这里线性关系是假设特征与结果满足线性关系。多数情况下,线性关系的表达能力非常强悍,每个特征对于结果的影响强弱可以由前面的参数所体现,且每个特征可以映射到一个函数,然后再参与线性计算。这样以来就可以表达特征与结果之间的非线性关系。

三、项目案例

1、项目案例之简单线性回归

1.1  项目概述

        根据下图散点图情况,给出该数据的最佳拟合直线。

          

1.2   样本数据样式(ex0.txt)

       

1	0.138306	3.149813
1	0.247809	3.476346
1	0.64827	4.119688
1	0.731209	4.282233
1	0.236833	3.486582
1	0.969788	4.655492
1	0.607492	3.965162
1	0.358622	3.5149
1	0.147846	3.125947

1.3  简单线性回归实现过程如下

         

# 简单线性回归函数和数据导入函数
from numpy import *
import matplotlib.pyplot as plt


def loadDataSet(fileName):
    """
    加载数据,解析以tab键分隔的文件中的浮点数。
    Returns:
             dataMat-- feature对应的数据集
             labelMat-- feature对应的分类标签,即类别标签   
    """
   
    numFeat = len(open(fileName).readline().split('\t')) - 1
    dataMat = []
    labelMat = []
    fr = open(fileName)
    for line in fr.readlines():
        lineArr = []  #读取每一行
        
        curLine = line.strip().split('\t')
        # i 从0 到2 ,不包括2
        for i in range(numFeat):
          
            lineArr.append(float(curLine[i]))
            
       
        dataMat.append(lineArr)
   
        labelMat.append(float(curLine[-1]))
        
    return dataMat, labelMat
    

def standRegres(xArr, yArr):
    """
    Descriptions: 线性回归
    Args:
            xArr-- 输入的样本数据,包含每个样本的feature
            yArr--  对应于输入数据的类别标签,即每个样本数据对应的目标变量
    Returns:
            ws --  回归系数
            
    """
    xMat = mat(xArr)
    yMat = mat(yArr).T
    xTx = xMat.T*xMat
   
    if linalg.det(xTx) == 0:
        print('This matrix is singular, cannot do inverse.')
        return 
    
    ws = xTx.I * (xMat.T*yMat)
    return ws
xArr, yArr = loadDataSet('ex0.txt')
ws = standRegres(xArr, yArr)
xMat = mat(xArr); yMat =mat(yArr) ; yHat = xMat*ws
fg = plt.figure()
ax = fg.add_subplot(111)
ax.scatter(xMat[:,1].flatten().A[0], yMat.T[:,0].flatten().A[0])
xCopy = xMat.copy()
xCopy.sort(0)
yHat = xCopy*ws
ax.plot(xCopy[:,1] , yHat)
plt.show()
得到如下图形效果:

    

2、 项目案例之局部加权线性回归         

2.1  概念理解

        线性回归中可能会遇到的一个问题是出现欠拟合现象,因为它要求的是具有最小均方误差的无偏估计。显然,如果模型欠拟合将不能取得最好的预测效果。所以有些方法允许引入一些偏差,进而降低预测的均方误差。这里的局部加权线性回归(Locally Weights Linear Regression, LWLR)就是其中方法之一。在该算法中,我们给予待预测点附近的每个点赋予一定的权重,与上面简单线性回归类似,基于这个子集上基于最小均方差来进行普通回归。需要进行最小化的目标函数如下:

        与KNN一样,这种算法每次预测均需要事先选取出对应的数据子集。该算法解出的回归系数w的形式如下:

        ,这里w 是一个矩阵,用来给每个数据点赋予权重。

         LWLR使用“核”(与支持向量机中的核类似)来对附近的点赋予更高的权重。核的类型可以自由选择,常用的核是高斯核,高斯核对应的权重如下:

             

        所以,这样我们就构建了一个只含对角元素的权重矩阵w,且点x与x(i)越近,w(i,i)将会越大。上面公式中包含一个需要用户指定的参数k,它就决定了对附近点赋予多大的权重,这也是局部加权线性回归唯一需要考虑的参数。下图展示了参数k与权重的关系。

     

     图中,每个点的权重图(假如我们预测的点是x=0.5),最上面的图是原始数据,第二个图显示了k=0.5时,大部分数据都用于训练回归模型;而最小面的显示当k=0.01时,仅有较少量的局部点被用于训练回归模型。

  

2.2  局部加权线性回归原理

  •       读取相关数据,将特征X、特征标签Y存储到矩阵x、y中;
  •       利用高斯核构建一个权重矩阵W,对预测值附近的点赋予权重;
  •        验证矩阵X^TX是否可逆;
  •        使用最小二乘法求得回归系数w的最佳估计。

2.3 局部加权线性回归实现过程(样本数据同样是ex0.txt)

      

  # 局部加权线性回归
def lwlr(testPoint,xArr,yArr,k=1.0):
    '''
        Description:
            局部加权线性回归,在待预测点附近的每个点赋予一定的权重,在子集上基于最小均方差来进行普通的回归。
        Args:
            testPoint:样本点
            xArr:样本的特征数据,即 feature
            yArr:每个样本对应的类别标签,即目标变量
            k:关于赋予权重矩阵的核的一个参数,与权重的衰减速率有关
        Returns:
            testPoint * ws:数据点与具有权重的系数相乘得到的预测点
    '''
    xMat = mat(xArr)
    yMat = mat(yArr).T
    m = shape(xMat)[0]
    # eye()返回一个对角线元素为1,其他元素为0的二维数组,创建权重矩阵weights,该矩阵为每个样本点初始化了一个权重                   
    weights = mat(eye((m)))
    for j in range(m):
        diffMat = testPoint - xMat[j,:]
        weights[j,j] = exp(diffMat*diffMat.T/(-2.0*k**2))
  
    xTx = xMat.T * (weights * xMat)
    if linalg.det(xTx) == 0.0:
        print ("This matrix is singular, cannot do inverse")
        return
    # 计算出回归系数的一个估计
    ws = xTx.I * (xMat.T * (weights * yMat))
    return testPoint * ws

def lwlrTest(testArr,xArr,yArr,k=1.0):
    '''
        Description:
            测试局部加权线性回归,对数据集中每个点调用 lwlr() 函数
        Args:
            testArr:测试所用的所有样本点
            xArr:样本的特征数据,即 feature
            yArr:每个样本对应的类别标签,即目标变量
            k:控制核函数的衰减速率
        Returns:
            yHat:预测点的估计值
    '''
    m = shape(testArr)[0]
    yHat = zeros(m)
    for i in range(m):
        yHat[i] = lwlr(testArr[i],xArr,yArr,k)
    # 返回估计值
    return yHat

def lwlrTestPlot(xArr,yArr,k=1.0):  
    '''
        Description:
            首先将 X 排序,其余的都与lwlrTest相同,这样更容易绘图
        Args:
            xArr:样本的特征数据,即 feature
            yArr:每个样本对应的类别标签,即目标变量,实际值
            k:控制核函数的衰减速率的有关参数,这里设定的是常量值 1
        Return:
            yHat:样本点的估计值
            xCopy:xArr的复制
    '''
    yHat = zeros(shape(yArr))
    xCopy = mat(xArr)
    xCopy.sort(0)
 
    for i in range(shape(xArr)[0]):
        yHat[i] = lwlr(xCopy[i],xArr,yArr,k)
    return yHat,xCopy


    #test for LWLR
def regression2():
    xArr, yArr = loadDataSet("ex0.txt")
    yHat = lwlrTest(xArr, xArr, yArr, 0.003)
    xMat = mat(xArr)
    srtInd = xMat[:,1].argsort(0)           #argsort()函数是将x中的元素从小到大排列,提取其对应的index(索引),然后输出
    xSort=xMat[srtInd][:,0,:]
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.plot(xSort[:,1], yHat[srtInd])
    ax.scatter([xMat[:,1].flatten().A[0]], [mat(yArr).T.flatten().A[0]] , s=2, c='red')
    plt.show()
 

xArr, yArr = loadDataSet('ex0.txt')
yHat = lwlrTest(xArr, xArr, yArr, 1.0)  #这里调整参数k,使其分别取1.0、0.01、0.003,得到三张不同的拟合图像。
xMat = mat(xArr)
srtInd = xMat[:,1].argsort(0)
xSort = xMat[srtInd][:, 0, :]
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(xSort[:,1],yHat[srtInd])
ax.scatter(xMat[:,1].flatten().A[0],mat(yArr).T.flatten().A[0], s = 2 ,c = 'red')
plt.show()

 2.4  模型效果展示即分析

这里调整参数k,使其分别取1.0、0.01、0.003,得到三张不同的拟合图像,分别如下

       

  对于模型效果分析如下:

       使用3种不同平滑值绘制出的局部加权线性回归结果中,上图k=1.0时的模型效果与最小二乘法差不多,k=0.01时该模型可以挖出数据的潜在规律,而k=0.003时则考虑了太多的噪声,出现了过拟合现象。

2.5  特别关注

        当然,我们在使用局部加权线性回归时,也会遇到一些问题,比如增加了计算量,因为它对每个点做预测时都会使用整个数据集。


3、项目案例之二--预测鲍鱼的年龄

3.1 项目概述

         在一份来自UCI数据集合的数据中,记录了鲍鱼(一种介壳类水生物)的年龄,依据鲍鱼壳的层数可以推算出鲍鱼的年龄。

3.2  样本数据的样式

        

1	0.43	0.35	0.11	0.406	0.1675	0.081	0.135	10
1	0.49	0.38	0.135	0.5415	0.2175	0.095	0.19	11
-1	0.535	0.405	0.145	0.6845	0.2725	0.171	0.205	10
-1	0.47	0.355	0.1	0.4755	0.1675	0.0805	0.185	10
1	0.5	0.4	0.13	0.6645	0.258	0.133	0.24	12
0	0.355	0.28	0.085	0.2905	0.095	0.0395	0.115	7
-1	0.44	0.34	0.1	0.451	0.188	0.087	0.13	10
1	0.365	0.295	0.08	0.2555	0.097	0.043	0.1	7
1	0.45	0.32	0.1	0.381	0.1705	0.075	0.115	9
1	0.355	0.28	0.095	0.2455	0.0955	0.062	0.075	11
0	0.38	0.275	0.1	0.2255	0.08	0.049	0.085	10

3.3 项目开发流程

        收集数据:     采用任意方法收集数据。

        准备数据:     回归需要数值型数据,标称数据将被转换为二值型数据。

        分析数据:     绘制出数据的可视化二维数据图将有助于对数据进行分析和理解。在使用缩减法获得新回归系数之后,将新得到的拟合直线绘制在图形上进行对比。

        训练数据:      求出回归系数。

       测试数据:       使用rssError()函数,计算预测误差的大小,用来分析模型的效果。

       使用数据:       使用回归,在给定输入时可以预测到一个值,这是对分类技术的提升,因为该算法可以预测连续型数据而不仅仅是离散型的分类标签。


3.4 实现过程

          

def rssError(yArr , yHatArr):
    """
    Descriptions:
       计算分析预测误差的大小
    Args:
         yArr--真实的目标变量
         yHatArr -- 预测得到的估计值
    Returns:
          计算真实值与估计值之间差的平方和并作为最后返回值
    """
    return ((yArr - yHatArr) **2).sum()
abX , abY = loadDataSet('abalone.txt')
oldyHat01 = lwlrTest(abX[:99] , abX[0:99] , abY[0:99] , 0.1)
oldyHat1 = lwlrTest(abX[:99] , abX[0:99] , abY[0:99] , 1)
oldyHat10 = lwlrTest(abX[:99] , abX[0:99] , abY[0:99] , 10)
oldyHat30 = lwlrTest(abX[:99] , abX[0:99] , abY[0:99] , 30)
oldyHat100 = lwlrTest(abX[:99] , abX[0:99] , abY[0:99] , 100)
    #打印出不同的核预测值与训练集上真实值之间的误差大小
print('Old yHat01 Error Size is :', rssError(abY[0:99] ,oldyHat01.T))
print ("Old yHat1 Error Size is :" , rssError(abY[0:99] , oldyHat1.T))
print ("Old yHat10 Error Size is :" , rssError(abY[0:99] , oldyHat10.T))
print ("Old yHat30 Error Size is :" , rssError(abY[0:99] , oldyHat30.T))   
print ("Old yHat100 Error Size is :" , rssError(abY[0:99] , oldyHat100.T))  

    #打印出 不同的核预测值与新数据集(预测数据集)上真实值之间的误差大小
newyHat01 = lwlrTest(abX[100:199] , abX[0:99] , abY[0:99] , 0.1)
print("New yHat01 Error Size is :" , rssError(abY[100:199] , newyHat01.T))
newyHat1 = lwlrTest(abX[100:199] , abX[0:99] , abY[0:99] , 1.0)
print ('New yHat1 Error Size is : ', rssError(abY[100:199] , newyHat1.T))
newyHat10 = lwlrTest(abX[100:199] , abX[0:99] , abY[0:99] , 10)
print ("New yHat10 Error Size is :" , rssError(abY[100:199] , newyHat10.T))
newyHat30 = lwlrTest(abX[100:199] , abX[0:99] , abY[0:99] , 30)
print ("New yHat30 Error Size is :" , rssError(abY[100:199] , newyHat30.T))
newyHat100 = lwlrTest(abX[100:199] , abX[0:99] , abY[0:99] , 100)
print ("New yHat100 Error Size is :" , rssError(abY[100:199] , newyHat100.T))  
    
    #使用简单的线性回归进行预测,与上面计算的结果进行对比
standWs = standRegres(abX[0:99] , abY[0:99])
standyHat = mat(abX[100:199]) * standWs
print ("Stand yHat Error Size is :" , rssError(abY[100:199] , standyHat.T.A))

3.5  模型效果展示及分析

        

Old yHat01 Error Size is : 56.7842091184
Old yHat1 Error Size is : 429.89056187
Old yHat10 Error Size is : 549.118170883
Old yHat30 Error Size is : 550.830189304
Old yHat100 Error Size is : 551.025658521
New yHat01 Error Size is : 25119.4591112
New yHat1 Error Size is :  573.52614419
New yHat10 Error Size is : 517.571190538
New yHat30 Error Size is : 518.515767738
New yHat100 Error Size is : 518.62544351
Stand yHat Error Size is : 518.636315325
     由以上结果,我们可以看到简单线性回归与局部加权线性回归具有类似的效果,这也说明一点,只有在未知数据上比较效果才能选取最佳模型。结合核大小为0.1、1.0、10、 30、 100 的误差大小,可知最佳核大小为10,此时误差最小。

     另一方面也说明:局部加权线性回归构建的模型比普通线性回归构建的模型效果较好,但局部加权线性回归也有个问题,就是每次都在整个数据集上运行。

3.6 特别说明:

             笔者接触到的书籍、网络技术文章,在处理此类问题时,基本上,在选择LWLR的“核”时都止于10,没有进行更多核的检验,对于结论也只是提到核大小为10可能是最佳,没有给出进一步验证,这里笔者进行了深入验证,根据验证结果,得本项目采用局部加权线性回归的最佳核大小为10。


4、 通过缩减系数来“理解”数据

         如果遇到特征数目比样本点还有多的情况,还使用之前的线性回归方法来预测目标值是不行的,因为在计算 时会出问题。

          在特征数多于样本点数(n>m)时,说明输入数据矩阵不是满秩矩阵,而非满秩矩阵求逆会出问题。

         为了解决这一问题,这里采用缩减系数来实现。所以,统计学家引入了岭回归(ridge regression)这样一个缩减方法,当然Lasso和前向逐步回归也都是大家所知道的缩减方法。但lasso方法计算很复杂,一般我们会采用前向逐步回归的方法来代替,可以得到与Lasso类似的效果且计算过程更易实现。

4.1     岭回归(ridge regression)

           岭回归就是在矩阵上增加一个λI,从而使得矩阵非异,进而对 求逆,其中 I  是一个m×m的单位矩阵,对角线上的元素全取1,其余元素为0。而 λ 参数是用户自定义的。此时,回归系数计算公式转换为:

             

            岭回归起初多数用来处理特征数多于样本数的问题,现在也用于在估计中引入偏差,从而得到更好的估计值。这里通过引入λ 来限制所有w之和,通过引入该惩罚项,可以减少不重要的参数,这个技术在统计学上叫缩减(shrinkage)。


4.2     套索方法(Lasso,The Least Absolute Shrinkage and Selection Operator)

           在增加如下约束条件,普通最小二乘法回归得到与岭回归一样的公式。

          

         上式限定了所有回归系数的平方和不能大于 λ 。使用普通的最小二乘法回归在当两个或更多的特征相关时,可能会得到一个很大的正系数和一个很大的负系数。正是因为上述限制条件的存在,使用岭回归可以避免这个问题。

与岭回归类似,另一个缩减方法lasso也对回归系数做了限定,对应的约束条件如下

         

       唯一的不同点在于,这个约束条件使用绝对值取代了平方和。虽然约束形式只是稍作变化,结果却大相径庭: 在 λ 足够小的时候,一些系数会因此被迫缩减到 0.这个特性可以帮助我们更好地理解数据。

4.3    前向逐步回归

           前向逐步回归算法可以得到与 lasso 差不多的效果,但更加简单。它属于一种贪心算法,即每一步都尽可能减少误差。一开始,所有权重都设置为 1,然后每一步所做的决策是对某个权重增加或减少一个很小的值。

   

5、   项目案例之预测鲍鱼年龄岭回归方法处理

5.1    预测鲍鱼年龄岭回归方法实现过程

      

#岭回归
def ridgeRegres(xMat , yMat , lam = 0.2):
    """
       Args:
        xMat--样本的特征数据,即feature
        yMat--每个样本的类别标签,即目标变量,实际值。
        lam-- 引入一个lambda,使得矩阵非奇异。
    Returns:
           经过岭回归公式计算得到的回归系数
    
    """
    xTx = xMat.T * xMat
  
    denom = xTx + eye(shape(xMat)[1]) * lam
 
    if linalg.det(denom) == 0.0:
        print("This matrix is singular , cannot do inverse.")
        return 
    
    ws = denom.I * (xMat.T * yMat)
    return ws

def ridgeTest(xArr , yArr):
    """
    Desc:
        ridgeTest()函数用于在一组λ上测试结果
    Args:
        xArr-- 样本特征数据,即feature
        yArr-- 每个样本的类别标签,即目标变量,实际值
    Returns:
        wMat -- 将计算得到的所有回归系数都输入到一个矩阵并返回
    
    """
    xMat = mat(xArr)
    yMat = mat(yArr).T
    #计算Y的均值
    yMean = mean(yMat , 0)

    yMat = yMat - yMean
 
    xMeans = mean(xMat , 0)

    xVar = var(xMat , 0)
 
    xMat = (xMat - xMeans)/xVar
    #可以在30个不同的lambda下调用ridgeRegres()函数
    numTestPts = 30
  
    wMat = zeros((numTestPts , shape(xMat)[1]))
    for i in range(numTestPts):
        ws = ridgeRegres(xMat , yMat, exp(i-10))
        wMat[i , :] = ws.T
    return wMat
abX , abY = loadDataSet('abalone.txt')
ridgeWeights = ridgeTest(abX , abY)
fig = plt. figure()
ax = fig.add_subplot(111)
ax.plot(ridgeWeights)
plt.show()

5.2  岭回归方法运行效果及分析

       

      

      从图中的效果来看, 该图绘出了回归系数与log(λ)的关系。在图形的最左边,此时 λ 其实是最小,可以得到所有回归系数的原始值(与简单线性回归一致);而在最右边,系数全部缩减为0;所以可知,在中间部分的某值可以使得模型预测最佳的效果最。为了得到最佳参数值,还需要进行交叉验证。要判断哪些变量对预测结果最具有影响力,在图中观察其对应的系数大小即可。

       当然,还有一些方法,比如 lasso、LAR、PCA回归以及子集选择等。与岭回归一样,这些方法不仅可以提供预测精确率,还可以解释回归系数。

6、 项目案例之预测鲍鱼年龄前向逐步回归方法处理

6.1  前向逐步回归实现过程

      

#前向逐步回归方法

def regularize(xMat): 
    inMat = xMat.copy()
    inMeans = mean(inMat,0)  
    inVar = var(inMat,0)  
    inMat = (inMat - inMeans)/inVar
    return inMat


def stageWise(xArr , yArr , eps = 0.01 , numIt = 100):
    xMat = mat(xArr) ; yMat = mat(yArr).T
    yMean = mean(yMat, 0)
    yMat = yMat - yMean 
    xMat = regularize(xMat)  
    m, n = shape(xMat)
    returnMat = zeros((numIt, n)) 
    ws = zeros((n,1)); wsTest = ws.copy() ; wsMax = ws.copy()
    for i in range(numIt):
        print (ws.T)
        lowestError = inf
        for j in range(n):
            for sign in [-1,1]:
                wsTest = ws.copy()
                wsTest[j] += sign*eps
                yTest = xMat * wsTest
                rssE = rssError (yMat.A, yTest.A)
                if rssE < lowestError:
                    lowestError = rssE
                    wsMax = wsTest
                    
        ws = wsMax.copy()
        returnMat[i,:] = ws.T
    return returnMat

6.2  前向逐步回归法运行效果及分析

         在eps设置为0.01且迭代次数为200的情况下,会发现,运行一段时间之后系数已经包含,就会在特定值之间来回震荡,这是因为步长太长的缘故,下面分别给出eps为0.01且迭代次数为200、eps为0.001且迭代此时为5000的运行数据结果、eps为0.005且迭代次数为1000之后的图像,如下:

        特别说明:由于下面展示数据效果占用篇幅较大,所以这里笔者将涉及数据展示中间部分数删除,但是效果和结论不受影响,如果有些读者不放心的话,可以自行运行代码查看完整数据效果展示情况。

      eps为0.01且迭代次数为200运行数据结果展示(因考虑整体篇幅问题,仅罗列部分数据):   

     

[[ 0.  0.  0.  0.  0.  0.  0.  0.]]
[[ 0.    0.    0.    0.01  0.    0.    0.    0.  ]]
[[ 0.    0.    0.    0.02  0.    0.    0.    0.  ]]
[[ 0.    0.    0.    0.03  0.    0.    0.    0.  ]]
[[ 0.    0.    0.    0.04  0.    0.    0.    0.  ]]
[[ 0.    0.    0.    0.05  0.    0.    0.    0.  ]]
[[ 0.    0.    0.    0.06  0.    0.    0.    0.  ]]
[[ 0.    0.    0.01  0.06  0.    0.    0.    0.  ]]
[[ 0.    0.    0.01  0.06  0.    0.    0.    0.01]]
[[ 0.    0.    0.01  0.06  0.    0.    0.    0.02]]
[[ 0.    0.    0.01  0.06  0.    0.    0.    0.03]]
[[ 0.    0.    0.01  0.06  0.    0.    0.    0.04]]
[[ 0.    0.    0.01  0.06  0.    0.    0.    0.05]]
[[ 0.    0.    0.01  0.06  0.    0.    0.    0.06]]
[[ 0.    0.    0.01  0.06  0.    0.    0.    0.07]]
[[ 0.    0.    0.01  0.06  0.    0.    0.    0.08]]
[[ 0.    0.    0.01  0.05  0.    0.    0.    0.08]]
[[ 0.    0.    0.01  0.05  0.    0.    0.    0.09]]
[[ 0.    0.    0.01  0.05  0.    0.    0.    0.1 ]]
[[ 0.    0.    0.01  0.05  0.    0.    0.    0.11]]
[[ 0.    0.    0.01  0.05  0.   -0.01  0.    0.11]]
[[ 0.    0.    0.01  0.05  0.   -0.02  0.    0.11]]
[[ 0.    0.    0.01  0.05  0.   -0.1   0.    0.17]]
[[ 0.    0.    0.01  0.05  0.   -0.11  0.    0.17]]
[[ 0.    0.    0.01  0.05  0.   -0.12  0.    0.17]]
[[ 0.    0.    0.01  0.05  0.   -0.12  0.    0.18]]
[[ 0.    0.    0.01  0.05  0.   -0.13  0.    0.18]]
[[ 0.    0.    0.01  0.05  0.   -0.13  0.    0.19]]
[[ 0.    0.    0.01  0.05  0.   -0.14  0.    0.19]]
[[ 0.    0.    0.01  0.05  0.   -0.23  0.    0.25]]
[[ 0.    0.    0.01  0.05  0.   -0.24  0.    0.25]]
[[ 0.    0.    0.01  0.05  0.   -0.25  0.    0.25]]
[[ 0.    0.    0.02  0.05  0.   -0.25  0.    0.25]]
[[ 0.    0.    0.02  0.04  0.   -0.25  0.    0.25]]
[[ 0.    0.    0.03  0.04  0.   -0.25  0.    0.25]]
[[ 0.    0.    0.05  0.04  0.   -0.37  0.    0.3 ]]
[[ 0.    0.    0.05  0.04  0.   -0.37  0.    0.31]]
[[ 0.    0.    0.05  0.04  0.   -0.38  0.    0.31]]
[[ 0.    0.    0.05  0.04  0.   -0.39  0.    0.31]]
[[ 0.    0.    0.06  0.04  0.   -0.39  0.    0.31]]
[[ 0.    0.    0.06  0.04  0.   -0.4   0.    0.31]]
[[ 0.    0.    0.06  0.04  0.   -0.41  0.    0.31]]
[[ 0.    0.    0.06  0.04  0.   -0.41  0.    0.32]]
[[ 0.    0.    0.06  0.04  0.   -0.42  0.    0.32]]
[[ 0.    0.    0.06  0.04  0.   -0.42  0.    0.33]]
.         .       .          .         .                .
.         .       .          .         .                .
.         .       .          .         .                .

array([[ 0.  ,  0.  ,  0.  , ...,  0.  ,  0.  ,  0.  ],
       [ 0.  ,  0.  ,  0.  , ...,  0.  ,  0.  ,  0.  ],
       [ 0.  ,  0.  ,  0.  , ...,  0.  ,  0.  ,  0.  ],
       ..., 
       [ 0.05,  0.  ,  0.09, ..., -0.64,  0.  ,  0.36],
       [ 0.04,  0.  ,  0.09, ..., -0.64,  0.  ,  0.36],
       [ 0.05,  0.  ,  0.09, ..., -0.64,  0.  ,  0.36]])



    eps为0.001且迭代此时为5000的运行数据结果展示因考虑整体篇幅问题,仅罗列部分数据)

       

[[ 0.  0.  0.  0.  0.  0.  0.  0.]]
[[ 0.     0.     0.     0.001  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.002  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.008  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.009  0.     0.     0.     0.   ]]
[[ 0.    0.    0.    0.01  0.    0.    0.    0.  ]]
[[ 0.     0.     0.     0.011  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.012  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.013  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.014  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.015  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.016  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.017  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.018  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.019  0.     0.     0.     0.   ]]
[[ 0.    0.    0.    0.02  0.    0.    0.    0.  ]]
[[ 0.     0.     0.     0.021  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.022  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.023  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.029  0.     0.     0.     0.   ]]
[[ 0.    0.    0.    0.03  0.    0.    0.    0.  ]]
[[ 0.     0.     0.     0.031  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.038  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.039  0.     0.     0.     0.   ]]
[[ 0.    0.    0.    0.04  0.    0.    0.    0.  ]]
[[ 0.     0.     0.     0.041  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.048  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.049  0.     0.     0.     0.   ]]
[[ 0.    0.    0.    0.05  0.    0.    0.    0.  ]]
[[ 0.     0.     0.     0.051  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.052  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.058  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.059  0.     0.     0.     0.   ]]
[[ 0.    0.    0.    0.06  0.    0.    0.    0.  ]]
[[ 0.     0.     0.     0.061  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.062  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.063  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.064  0.     0.     0.     0.   ]]
[[ 0.     0.     0.     0.065  0.     0.     0.     0.   ]]
[[ 0.     0.     0.001  0.065  0.     0.     0.     0.   ]]
[[ 0.     0.     0.002  0.065  0.     0.     0.     0.   ]]
[[ 0.     0.     0.003  0.065  0.     0.     0.     0.   ]]
[[ 0.     0.     0.004  0.065  0.     0.     0.     0.   ]]
[[ 0.     0.     0.005  0.065  0.     0.     0.     0.02 ]]
[[ 0.     0.     0.005  0.065  0.     0.     0.     0.021]]
[[ 0.     0.     0.005  0.065  0.     0.     0.     0.022]]
[[ 0.     0.     0.005  0.065  0.     0.     0.     0.023]]
[[ 0.     0.     0.005  0.065  0.     0.     0.     0.024]]
[[ 0.     0.     0.005  0.065  0.     0.     0.     0.025]]
[[ 0.     0.     0.005  0.065  0.     0.     0.     0.026]]
    .            .        .       .             .            .           .             .
    .            .        .       .             .            .           .             .
    .            .        .       .             .            .           .             .
[[ 0.043 -0.011  0.12   0.022  2.023 -0.963 -0.105  0.187]]
[[ 0.044 -0.011  0.12   0.022  2.023 -0.963 -0.105  0.187]]
[[ 0.043 -0.011  0.12   0.022  2.023 -0.963 -0.105  0.187]]
[[ 0.044 -0.011  0.12   0.022  2.023 -0.963 -0.105  0.187]]

array([[ 0.   ,  0.   ,  0.   , ...,  0.   ,  0.   ,  0.   ],
       [ 0.   ,  0.   ,  0.   , ...,  0.   ,  0.   ,  0.   ],
       [ 0.   ,  0.   ,  0.   , ...,  0.   ,  0.   ,  0.   ],
       ..., 
       [ 0.043, -0.011,  0.12 , ..., -0.963, -0.105,  0.187],
       [ 0.044, -0.011,  0.12 , ..., -0.963, -0.105,  0.187],
       [ 0.043, -0.011,  0.12 , ..., -0.963, -0.105,  0.187]])




     eps为0.005且迭代次数为1000之后的图像展示:



           逐步线性回归算法的主要优点在于它可以帮助人们理解现有的模型并作出改进。当构建了一个模型后,可以运行该算法找出重要的特征,这样就有可能及时停 止对那些不重要特征的收集。最后,如果用于测试,该算法每100次迭代后就可以构建出一个模型,可以使用类似于10折交叉验证的方法比较这些模型,最终选 择使误差最小的模型。


四、权衡偏差及方差

      任何时候,一旦发现模型和测量值之间存在差异,就说出现了误差。当考虑模型中的 “噪声” 或者说误差时,必须考虑其来源。你可能会对复杂的过程进行简化,这将导致在模型和测量值之间出现 “噪声” 或误差,若无法理解数据的真实生成过程,也会导致差异的产生。另外,测量过程本身也可能产生 “噪声” 或者问题。下面我们举一个例子,我们使用 线性回归 和 局部加权线性回归 处理过一个从文件导入的二维数据。

           

         其中的 N(0, 1) 是一个均值为 0、方差为 1 的正态分布。我们尝试过禁用一条直线来拟合上述数据。不难想到,直线所能得到的最佳拟合应该是 3.0+1.7x 这一部分。这样的话,误差部分就是 0.1sin(30x)+0.06N(0, 1) 。在上面,我们使用了局部加权线性回归来试图捕捉数据背后的结构。该结构拟合起来有一定的难度,因此我们测试了多组不同的局部权重来找到具有最小测试误差 的解。
        下图给出了训练误差和测试误差的曲线图,上面的曲面就是测试误差,下面的曲线是训练误差。我们根据 预测鲍鱼年龄 的实验知道: 如果降低核的大小,那么训练误差将变小。从下图开看,从左到右就表示了核逐渐减小的过程。

      


      一般认为,上述两种误差由三个部分组成: 偏差、测量误差和随机噪声。局部加权线性回归 和 预测鲍鱼年龄 中,我们通过引入了三个越来越小的核来不断增大模型的方差。
         在缩减系数来“理解”数据这一节中,我们介绍了缩减法,可以将一些系数缩减成很小的值或直接缩减为 0 ,这是一个增大模型偏差的例子。通过把一些特征的回归系数缩减到 0 ,同时也就减小了模型的复杂度。例子中有 8 个特征,消除其中两个后不仅使模型更易理解,同时还降低了预测误差。对照上图,左侧是参数缩减过于严厉的结果,而右侧是无缩减的效果。
 
       方差是可以度量的。如果从鲍鱼数据中取一个随机样本集(例如取其中 100 个数据)并用线性模型拟合,将会得到一组回归系数。同理,再取出另一组随机样本集并拟合,将会得到另一组回归系数。这些系数间的差异大小也就是模型方差的反映。

 

五、总结

         回归与分类一样,也是预测目标值的过程。回归与分类的不同在于:前者预测连续型变量,后者预测离散型变量。其实,在统计学中,回归是最有力的工具之一。在回归方程里,求得特征对应的最佳回归系数的方法是最小化误差平方和。在给定输入矩阵X中,若可求逆的话,回归方法可以直接使用。数据集上求得的回归系数未必是最佳的,可以采用预测值yHat和实际值(即原始值)进行相关性度量来衡量回归系数的好坏情况。

        当遇到特征数多于样本点数时,矩阵不可以直接求逆。即使特征数少于样板点数时,矩阵也可能无法直接计算求逆,这可能是因为相关特征存在高度相关,此时采用岭回归方法处理。

       缩减方法:岭回归是缩减方法之一,是相当于限制了回归系数的大小。除去岭回归之外,还有lasso、LAR、PCA回归以及子集选择等。缩减方法可以看做是多模型增加偏差的同时减少方差,为了得到预测效果更好的模型,我们需要对于偏差和方差进行折中评估。

       关于线性回归模型分析的情况,先介绍到这里,后续有机会针对非线性回归模型的分析做单独撰写。

猜你喜欢

转载自blog.csdn.net/qq_37608890/article/details/78965441