如果数据特征比样本点还多,是不可以使用线性回归的,因为在计算的时候会出错。
如果特征比样本点还多(n>m),也就是说输入数据的矩阵x不是满秩矩阵。非满秩矩阵在求逆时会出问题。
为了解决上述问题,可以使用岭回归,接着是lasso法,该方法效果很好但是计算复杂。还有前向逐步回归,可以得到与lasso相似的效果,且更容易实现。
岭回归
简单来说,岭回归就是在矩阵上加一个从而使得矩阵非奇异,进而能对+求逆。其中矩阵I是一个m*m的单位矩阵,对角线上元素全是1,其他元素全为0。而是一个用户定义的数值。在这种情况下,回归系数的计算公式将变成:
岭回归最先用来处理特征数多余样本数的情况,现在也用于子啊估计中加入偏差,从而得到更好的估计。这里通过引入来限制了所有w之和,通过引入该惩罚项,能够减少不必要的参数,这个技术在统计学中也叫做缩减。
缩减方法可以去掉不重要的参数,因此能更好地理解数据。此外,和简单的线性回归相比,错减法能取得更好的预测效果。
与之前用到的训练其他参数所用方法类似,通过预测误差最小化得到:数据获取之后,首先抽一部分数据用于测试,剩余的作为训练集用于训练参数w。训练完毕后再测试集上测试预测性能。通过选取不同的来重复上述测试过程,最终得到一个使预测误差最小的。
岭回归代码实现:
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('行列式为0') return ws=denom.I*(xMat.T*yMat) return ws def 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 numTestPets=30 wMat=zeros((numTestPets,shape(xMat)[1])) for i in range(numTestPets): ws=ridgeRegres(xMat,yMat,exp(i-10)) wMat[i,:]=ws.T return wMat
上述代码包含两个函数:ridgeRegres()用于计算回归系数,而函数ridgeTest()用于在一组上测试结果。
第一个函数ridgeRegres()实现了给定lambda下的岭回归求解。如果没有指定lambda,则默认为0.2。由于lambda是Python保留的关键字,所以代码中用lam代替。函数首先构建矩阵,然后用lam乘以单位矩阵。在普通回归方法可能会产生错误时,岭回归仍然可以正常工作。这里需要做一个检查。最后,如果矩阵非奇异就计算回归系数并返回。
为了使用岭回归和缩减技术,首先需要对特征做标准化处理,使每维特征具有相同的重要性。ridgeRegres()展示了数据标准化过程。具体的做法就是所有特征都减去各自的均值并除以方差。
处理完成后就可以在30个不同lambda下调用ridgeRegres()函数。这里的lambda应该以指数级变化,这样可以看出lambda在取非常小的值时和取非常大的值时分别对结果造成的影响。最后将所有的回归系数输出到一个矩阵并返回。
尝试在鲍鱼数据集上运行:
abX,abY=loadDataSet('abalone.txt') ridgeWeights=ridgeTest(abX,abY) import matplotlib.pyplot as plt fig=plt.figure() ax=fig.add_subplot(111) ax.plot(ridgeWeights) plt.show()
上图绘制出了回归系数与的关系。在最左边,即最小时,可以得到所有系数的原始值(与线性回归一致),而在右边,系数全都缩减为0;在中间部分的某值将可以取得最好的预测效果。为了定量的找到最佳参数值,还需要进行交叉验证。另外还要判断哪些变量对结果预测最具有影响力。
lasso
不难证明,在增加下面的约束时,普通的最小二乘法回归会得到与岭回归一样的公式:
上面的表达式限定了所有回归系数的平方和不能大于。使用最小二乘法回归在当两个或更多的特征相关时,可能会得出一个很大的正系数和一个很大的负系数。正式因为上述限制条件的存在,使得岭回归可以避免这个问题。
与岭回归类似,另一个缩减方法lasso也对回归系数做了限定,限定条件如下:
唯一不同的点在于:这个约束条件用绝对值代替了平方和。虽然约束形式只是稍作变化,结果却完全不同:在足够小的时候,一些系数会因此被迫缩减到0,这个特性可以帮助我们更好地理解数据。这两个约束条件在看起来相差无几,但是极大地增加了计算复杂度。
前向逐步回归
前向逐步回归算法可以得到与lasso差不多的效果,但更简单。它属于一种贪心算法,即每一步都尽可能减少误差。一开始,所有的权重都设置为1,然后每一步所做的决策是对某个权重增加或者减少到一个很小的值。
该算法的伪代码如下:
数据标准化,使其分布满足0均值和单位方差
在每轮迭代过程中:
设置当前最小误差lowestError为正无穷
对每个特征:
增大或缩小:
改变一个系数得到一个新的w
计算新w下的误差
如果误差Error小于当前最小误差lowestError:设置Wbest等于当前的W
将w设置为新的Wbest
实际代码实现:
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来保存w的值 ws=zeros((n,1)) wsTest=ws.copy() wsMax=ws.copy() #循环numIt次,并且在每次迭代时都打印出w向量,用于分析算法执行的过程和效果 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]=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
上述stageWise()是一个逐步线性回归算法的实现,它与lasso做法相近但计算简单。该函数的输入包括:输入数据xArr和预测变量yArr。此外还有两个参数:一个是eps,表示每次迭代需要调整的步长;另一个是numIt,表示迭代次数。
函数首先将输入数据转换并存入矩阵中,然后把特征按照均值为0方差为1进行标准化处理。在这之后创建了一个向量ws来保存w的值,并且为了实现贪心算法建立了ws的两份副本。接下来的优化过程需要迭代numIt次,并且在每次迭代时都打印出w向量,用于分析算法执行的过程和效果。
贪心算法在所有特征上运行两次for循环,分别计算增加或减少该特征对误差的影响。这里使用的是平方误差,通过之前的函数rssError()得到。该误差初始值设为正无穷,经过与所有的误差比较后取最小的误差,整个过程循环迭代进行。
xArr,yArr=loadDataSet('abalone.txt') print(stageWise(xArr,yArr,0.01,200))
上述结果中,w1和w6都是0,表示他们不对目标值造成任何影响,也就是说这些特征很可能是不需要的。另外,在参数eps设置为0.01时,一段时间后系数就已经饱和并在特定值之间来回震荡,这是因为步长太大的缘故。
xMat=mat(xArr) yMat=mat(yArr).T yM=mean(yMat,0) yMat=yMat-yM weights=standRegres(xMat,yMat.T) print(weights.T)
逐步线性回归算法的主要优点在于它可以帮助人们理解现有的模型并做出改进,当构建了一个模型后,可以运算该算法找出重要的特征,这样就有可能及时停止对那些不重要特征的收集。最后,如果用于测试,该算法每100次迭代后就可以构建出一个模型,可以使用类似于10折交叉验证的方法比较这些模型,最终选择使误差最小的模型。
当应用缩减方法(比如逐步线性回归或岭回归)时,模型也就增加了偏差,与此同时却减小了模型的方差。