首页 » 机器学习实战 » 机器学习实战全文在线阅读

《机器学习实战》8.4 缩减系数来“理解”数据

关灯直达底部

如果数据的特征比样本点还多应该怎么办?是否还可以使用线性回归和之前的方法来做预测?答案是否定的,即不能再使用前面介绍的方法。这是因为在计算(XTX)-1的时候会出错。

如果特征比样本点还多(n > m),也就是说输入数据的矩阵X不是满秩矩阵。非满秩矩阵在求逆时会出现问题。

为了解决这个问题,统计学家引入了岭回归(ridge regression)的概念,这就是本节将介绍的第一种缩减方法。接着是lasso法,该方法效果很好但计算复杂。本节最后介绍了第二种缩减方法,称为前向逐步回归,可以得到与lasso差不多的效果,且更容易实现。

8.4.1 岭回归

简单说来,岭回归就是在矩阵XTX上加一个λI从而使得矩阵非奇异,进而能对XTX + λI求逆。其中矩阵I是一个m×m的单位矩阵,对角线上元素全为1,其他元素全为0。而λ是一个用户定义的数值,后面会做介绍。在这种情况下,回归系数的计算公式将变成:

岭回归最先用来处理特征数多于样本数的情况,现在也用于在估计中加入偏差,从而得到更好的估计。这里通过引入λ来限制了所有w之和 ,通过引入该惩罚项,能够减少不重要的参数,这个技术在统计学中也叫做缩减(shrinkage)。

岭回归中的岭是什么?

岭回归使用了单位矩阵乘以常量λ,我们观察其中的单位矩阵I,可以看到值1贯穿整个对角线,其余元素全是0。形象地,在0构成的平面上有一条1组成的“岭”,这就是岭回归中的“岭”的由来。

缩减方法可以去掉不重要的参数,因此能更好地理解数据。此外,与简单的线性回归相比,缩减法能取得更好的预测效果。

与前几章里训练其他参数所用的方法类似,这里通过预测误差最小化得到λ:数据获取之后,首先抽一部分数据用于测试,剩余的作为训练集用于训练参数w。训练完毕后在测试集上测试预测性能。通过选取不同的λ来重复上述测试过程,最终得到一个使预测误差最小的λ

下面看看实际效果,打开regression.py文件并添加程序清单8-3的代码。

程序清单8-3 岭回归

def ridgeRegres(xMat,yMat,lam=0.2):    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 wsdef ridgeTest(xArr,yArr):    xMat = mat(xArr); yMat=mat(yArr).T    yMean = mean(yMat,0)    #❶  数据标准化     yMat = yMat - yMean    xMeans = mean(xMat,0)    xVar = var(xMat,0)    xMat = (xMat - xMeans)/xVar    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   

程序清单8-3中的代码包含了两个函数:函数ridgeRegres用于计算回归系数,而函数ridgeTest用于在一组λ上测试结果。

第一个函数ridgeRegres实现了给定lambda下的岭回归求解。如果没指定lambda,则默认为0.2。由于lambda是Python保留的关键字,因此程序中使用了lam来代替。该函数首先构建矩阵XTX,然后用lam乘以单位矩阵(可调用NumPy库中的方法eye来生成)。在普通回归方法可能会产生错误时,岭回归仍可以正常工作。那么是不是就不再需要检查行列式是否为零,对吗?不完全对,如果lambda设定为0的时候一样可能会产生错误,所以这里仍需要做一个检查。最后,如果矩阵非奇异就计算回归系数并返回。

为了使用岭回归和缩减技术,首先需要对特征做标准化处理。回忆一下,第2章已经用过标准化处理技术,使每维特征具有相同的重要性(不考虑特征代表什么)。程序清单8-3中的第二个函数ridgeTest就展示了数据标准化的过程。具体的做法是所有特征都减去各自的均值并除以方差❶。

处理完成后就可以在30个不同的lambda下调用ridgeRegres函数。注意,这里的lambda应以指数级变化,这样可以看出lambda在取非常小的值时和取非常大的值时分别对结果造成的影响。最后将所有的回归系数输出到一个矩阵并返回。

下面看一下鲍鱼数据集上的运行结果。

>>> reload(regression)>>> abX,abY=regression.loadDataSet(/'abalone.txt/')>>> ridgeWeights=regression.ridgeTest(abX,abY)    

这样就得到了30个不同lambda所对应的回归系数。为了看到缩减的效果,在Python提示符下输入如下代码:

>>> import matplotlib.pyplot as plt>>> fig = plt.figure>>> ax = fig.add_subplot(111)>>> ax.plot(ridgeWeights)>>> plt.show     

运行之后应该看到一个类似图8-6的结果图,该图绘出了回归系数与log(λ)的关系。在最左边,即λ最小时,可以得到所有系数的原始值(与线性回归一致);而在右边,系数全部缩减成0;在中间部分的某值将可以取得最好的预测效果。为了定量地找到最佳参数值,还需要进行交叉验证。另外,要判断哪些变量对结果预测最具有影响力,在图8-6中观察它们对应的系数大小就可以。

图8-6 岭回归的回归系数变化图。λ非常小时,系数与普通回归一样。而λ非常大时,所有回归系数缩减为0。可以在中间某处找到使得预测的结果最好的λ值

还有一些其他缩减方法,如lasso、LAR、PCA回归1以及子集选择等。与岭回归一样,这些方法不仅可以提高预测精确率,而且可以解释回归系数。下面将对lasso方法稍作介绍。

1. Trevor Hastie, Robert Tibshirani, and Jerome Friedman, The Elements of Statistical Learning: Data Mining, Infer- ence, and Prediction, 2nd ed. (Springer, 2009).

8.4.2 lasso

不难证明,在增加如下约束时,普通的最小二乘法回归会得到与岭回归的一样的公式:

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

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

唯一的不同点在于,这个约束条件使用绝对值取代了平方和。虽然约束形式只是稍作变化,结果却大相径庭:在λ足够小的时候,一些系数会因此被迫缩减到0,这个特性可以帮助我们更好地理解数据。这两个约束条件在公式上看起来相差无几,但细微的变化却极大地增加了计算复杂度(为了在这个新的约束条件下解出回归系数,需要使用二次规划算法)。下面将介绍一个更为简单的方法来得到结果,该方法叫做前向逐步回归。

8.4.3 前向逐步回归

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

该算法的伪代码如下所示:

数据标准化,使其分布满足0均值和单位方差在每轮迭代过程中:    设置当前最小误差lowestError为正无穷    对每个特征:        增大或缩小:            改变一个系数得到一个新的W            计算新W下的误差            如果误差Error小于当前最小误差lowestError:设置Wbest等于当前的W        将W设置为新的Wbest    

下面看看实际效果,打开regression.py文件并加入下列程序清单中的代码。

程序清单8-4 前向逐步线性回归

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] += eps*sign                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   

程序清单8-4中的函数stageWise是一个逐步线性回归算法的实现,它与lasso做法相近但计算简单。该函数的输入包括:输入数据 xArr和预测变量yArr。此外还有两个参数:一个是eps,表示每次迭代需要调整的步长;另一个是numIt,表示迭代次数。

函数首先将输入数据转换并存入矩阵中,然后把特征按照均值为0方差为1进行标准化处理。在这之后创建了一个向量ws来保存w的值,并且为了实现贪心算法建立了ws的两份副本。接下来的优化过程需要迭代numIt次,并且在每次迭代时都打印出w向量,用于分析算法执行的过程和效果。

贪心算法在所有特征上运行两次for循环,分别计算增加或减少该特征对误差的影响。这里使用的是平方误差,通过之前的函数rssError得到。该误差初始值设为正无穷,经过与所有的误差比较后取最小的误差。整个过程循环迭代进行。

下面看一下实际效果,在regression.py里输入程序清单8-4的代码并保存,然后在Python提示符下输入如下命令:

>>> reload(regression)<module /'regression/' from /'regression.pyc/'>>>> xArr,yArr=regression.loadDataSet(/'abalone.txt/')>>> regression.stageWise(xArr,yArr,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.04   0.     0.09   0.03   0.31 -0.64   0.     0.36]][[ 0.05   0.     0.09   0.03   0.31 -0.64   0.     0.36]][[ 0.04   0.     0.09   0.03   0.31 -0.64   0.     0.36]]  

上述结果中值得注意的是w1w6都是0,这表明它们不对目标值造成任何影响,也就是说这些特征很可能是不需要的。另外,在参数eps设置为0.01的情况下,一段时间后系数就已经饱和并在特定值之间来回震荡,这是因为步长太大的缘故。这里会看到,第一个权重在0.04和0.05之间来回震荡。

下面试着用更小的步长和更多的步数:

>>> regression.stageWise(xArr,yArr,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.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]]  

接下来把这些结果与最小二乘法进行比较,后者的结果可以通过如下代码获得:

>>> xMat=mat(xArr)>>> yMat=mat(yArr).T>>> xMat=regression.regularize(xMat)>>> yM = mean(yMat,0)>>> yMat = yMat - yM>>> weights=regression.standRegres(xMat,yMat.T)>>> weights.Tmatrix([[ 0.0430442 , -0.02274163, 0.13214087, 0.02075182, 2.22403814,         -0.99895312, -0.11725427, 0.16622915]])  

可以看到在5000次迭代以后,逐步线性回归算法与常规的最小二乘法效果类似。使用0.005的epsilon值并经过1000次迭代后的结果参见图8-7。

图8-7 鲍鱼数据集上执行逐步线性回归法得到的系数与迭代次数间的关系。逐步线性回归得到了与lasso相似的结果,但计算起来更加简便

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

当应用缩减方法(如逐步线性回归或岭回归)时,模型也就增加了偏差(bias),与此同时却减小了模型的方差。下一节将揭示这些概念之间的关系并分析它们对结果的影响。