线性模型(linear model)就是试图通过属性的线性组合来进行预测的函数,基本形式如下:
线性回归要求均方误差最小:
我们把上式写成矩阵的形式:
最小二乘法的代码如下:
def standRegres(xArr,yArr):
xMat = mat(xArr); yMat = mat(yArr).T
xTx = xMat.T*xMat
if linalg.det(xTx) == 0.0:
print "This matrix is singular, cannot do inverse"
return
ws = xTx.I * (xMat.T*yMat)
return ws
然而,现实情况是很多时候, XTX 并不满秩,这样 w 的解可能有多个解,它们都能使得均方误差最小化。碰到数据特征比样本数还要多的情况,一种方案是引入正则化(regularization)项。另一种方案是可以“缩减系数”,比如用岭回归(ridge regression)、LASSO法(该方法效果好但是计算复杂),前向逐步回归(可以得到与LASSO差不多的效果,但更容易实现)、LAR、PCA回归等。
我们在介绍岭回归之前先看一下局部加权线性回归。这首针对线性回归容易出现“欠拟合”现象提出的,因为它求的是具有最小均方误差的无偏估计。如果模型欠拟合将不能取得最好的效果。所以有些方法在估计中引入一些偏差,从而降低均方误差。其中一个方法就是局部加权线性回归(Locally Wegihted Linear Regression,LWLR),我们给待预测点附近的每个点赋予一定的权重。回归系数 w 形式如下:
def lwlr(testPoint,xArr,yArr,k=1.0):
xMat = mat(xArr); yMat = mat(yArr).T
m = shape(xMat)[0]
weights = mat(eye((m)))
for j in range(m): #next 2 lines create weights matrix
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
文末给出的下载链接中regreesion.py文件中,有个线性加权回归例子。
就是在 XTX 加上一个 λI 使得矩阵非奇异,这样 XTX+λI 就可以求逆了。于是:
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 ws
def ridgeTest(xArr,yArr):
xMat = mat(xArr); yMat=mat(yArr).T
yMean = mean(yMat,0)
yMat = yMat - yMean #to eliminate X0 take mean off of Y
#regularize X's
xMeans = mean(xMat,0) #calc mean then subtract it off
xVar = var(xMat,0) #calc variance of Xi then divide by it
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
ridgeRegres用于计算回归系数,ridgeTest用于在一组 λ 上测试结果。
同样在使用特征时,都要先进行归一化处理。
LASSO是least absolute shrinkage and selection operator的简称。我们注意到增加如下约束,最小二乘法回归会得到与岭回归一样的公式:
虽然用绝对值取代了平方和,但结果却差别很大。在 λ 足够小的时候,一些系数会因此被迫缩减为0,这样结果就有很好的稀疏性,这个特性可以帮助我们更好地理解数据。两个看起来相差无几,但细微的变化却极大增加了计算复杂度。为了在此约束下求解,需要使用二次规划算法。
前向逐步回归可以得到与LASSO差不多的结果,但更加简单,是一种贪心算法,每一步都尽可能减少误差。一开始,所有权重设为1,然后每一步所做的决策是对某个权重增加或减少一个很小的值。
代码如下:
def stageWise(xArr,yArr,eps=0.01,numIt=100):
xMat = mat(xArr); yMat=mat(yArr).T
yMean = mean(yMat,0)
yMat = yMat - yMean #can also regularize ys but will get smaller coef
xMat = regularize(xMat)
m,n=shape(xMat)
returnMat = zeros((numIt,n)) #testing code remove
ws = zeros((n,1)); wsTest = ws.copy(); wsMax = ws.copy()
for i in range(numIt):#could change this to while loop
#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
逐步线性回归主要优点在于它可以帮助人们理解现有模型并做出改进。当构建一个模型后,运行该算法找出重要的特征,这样就可以及时停止对那些不重要特征的收集。当应用缩减方法时,模型增加了偏差(bias),也减少了方差。
上面是回归任务,如果我们需要的是分类任务呢?我们只需要找一个单调可微函数将分类任务的真实标记与线性回归的预测值就可以了。
二分类最理想的是“单位阶跃函数”(unit-step function)。但是此函数不连续,于是我们希望找一个单调可微的替代函数:
在此处我们简便起见,直接采用梯度上升算法。我们知道梯度算子总是指向函数值最快的方向。我们每次向增长最快的方向移动一个值,称为步长,记为 a ,所以梯度上升算法的公式为:
def gradAscent(dataMatIn, classLabels):
dataMatrix = mat(dataMatIn)
labelMat = mat(classLabels).transpose()
m,n = shape(dataMatrix)
alpha = 0.001
maxCycles = 500
weights = ones((n,1))
for k in range(maxCycles):
h = sigmoid(dataMatrix*weights)
error = (labelMat - h) #vector subtraction
weights = weights + alpha * dataMatrix.transpose()* error
return weights
代码中求导不好操作,这里我们用差分可以起到同样的效果。
梯度上升算法在每次更新回归系数时要遍历整个数据集,改进方法是一次仅用一个样本点来更新回归系数,称为“随机梯度上升算法”,这是一种在线算法。上述代码仍有改进之处,比如 a 在每次迭代时候都会调整,这会缓解数据波动或者高频波动。 a 会随着得到次数不断减小,但永远不会减到0,这样多次迭代之后新数据仍具有一定的影响。
改进后的随机梯度上升代码如下:
def stocGradAscent(dataMatrix, classLabels, numIter=150):
m,n = shape(dataMatrix)
weights = ones(n) #initialize to all ones
for j in range(numIter):
dataIndex = range(m)
for i in range(m):
alpha = 4/(1.0+j+i)+0.0001 #apha decreases with iteration, does not
randIndex = int(random.uniform(0,len(dataIndex)))#go to 0 because of the constant
h = sigmoid(sum(dataMatrix[randIndex]*weights))
error = classLabels[randIndex] - h
weights = weights + alpha * error * dataMatrix[randIndex]
del(dataIndex[randIndex])
return weights
这里用个例子来使用Logistic回归来预测患有疝病的马存活问题。但是我们发现了样本数据缺失问题,通常的解决方法有:
这里我们全部用0来代替缺失值,这样在更新时不会影响系数。即特征对应0时,系数将保持0.5。我们在数据中还发现了标签缺失,这个在强化学习中可以通过更靠近某些标签就将其标记为那个标签,在本例中由于此类较少,我们直接丢弃。
(Linear Discriminant Analysis,LDA)与LDA(Latent Dirichlet Allocation)算法不要搞混了哦,虽然简写都是一样的。
LDA的思想很简单:设法将样例投影到一条直线上,使得同类样例的投影点近可能接近,异类样例的投影尽可能原理,在对新样本进行分类时,将其投影到这条直线上,再根据投影点位置来确定分类。
二分类问题推广到多分类主要用拆解法,主要策略有:
- 一对一
- 一对多
- 多对多
这样那个容易碰到类别不平衡问题,就是某类的数据量特别大或者特别少。
最后就是本文数据集和代码的下载地址啦,请点击这里。