这是基于《机器学习实战》一书的第八章内容总结而成的知识,有一些案例和相关的代码,即可获取。
8.1 用线性回归找到最佳拟合曲线
假设输入数据存放在矩阵X中,而回归系数存放在向量w中,那么对于给定的数据X1,预测结果将会通过Y1=X1.T×w给出。如何找出误差最小的W,一般采用平方误差最小,即最小二乘法。平方误差可以写做:
用矩阵表示还可以写做(y-x*w).T*(y-x*w)。如果对w求导,得到x.T*(y-xw),令其等于零,得到:
w帽即为当前估计出的w的最优值,表示最佳估计。一般数据矩阵x并非有逆,而公式中需要对x.T*x求逆,一般指伪逆,python的numpy包中,linalg函数有函数pinv求伪逆,也即x.T*x的逆。
拟合数据:
-
-
-
- from numpy import *
- from sklearn import linear_model
-
- def loadDataSet(fileName):
- numFeat = len(open(fileName).readline().split("\t"))-1
- dataMat = []
- labelMat = []
- fr = open(fileName)
- for line in fr.readlines():
- lineArr = []
- curLine = line.strip().split("\t")
- 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):
- 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"
- ws = linalg.pinv(xTx)*(xMat.T*yMat)
- return ws
- ws = xTx.I*(xMat.T*yMat)
- return ws
-
- xArr, yArr = loadDataSet("ex0.txt")
- ws = standRegres(xArr, yArr)
- print "最小二乘法得到的回归系数:\n",ws
-
- xMat = mat(xArr)
- yMat = mat(yArr)
- yHat = xMat*ws
-
-
- import matplotlib.pyplot as plt
- fig = plt.figure()
- ax = fig.add_subplot(111)
- ax.scatter(xMat[:,1].flatten().A[0], yMat.T[:,0].flatten().A[0])
- xCopy = xMat.copy()
- yHat = xCopy*ws
- ax.plot(xCopy[:,1], yHat)
- plt.show()
-
-
- yHat = xMat*ws
- print "皮尔逊相关系数计算预测值和真实值之间的相关性:\n",corrcoef(yHat.T, yMat)
-
-
- clf = linear_model.LinearRegression(fit_intercept=False)
- clf.fit(xArr,yArr)
- print "sklearn 里面线性回归训练得到的回归系数:\n",clf.coef_
得到的系数即预测值与真实值之间的相关性:
Figure 8-1: 预测得到的回归系数
可以看出sklearn里面预测的和计算的回归系数是一样的。
Figure 8-2: 拟合曲线及原始数据的散点图
python sklearn包中有计算线性回归的机器学习算法:
Figure 8-3: sklearn 计算线性回归方程
8.2 局部加权线性回归
局部加权线性回归(Locally Weighted Linear Regression, LWLR)
给待预测点附近的每个点赋予一定的权重,然后在这个子集上基于最小均方差来进行普通的回归。算法预测每次均需要事先取出对应的数据子集,解出的回归系数w的形式如下:
其中W是一个矩阵,用来给每个数据点赋予权重。
LWLR使用“核”来对附近的点赋予更高的权重,该加权模型认为样本点距离越近,越可能符合同一个线性模型。核的类型通常用高斯核。权重计算如下:
权重矩阵w为对角阵,点x与x(i)越近,w(i,i)将会越大。
coding:
-
- 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):
- 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"
- ws = linalg.pinv(xTx)*(xMat.T*(weights*yMat))
- ws =xTx.I*(xMat.T*(weights*yMat))
- return testPoint*ws
-
- def lwlrTest(testArr, xArr, yArr, k=1.0):
- m = shape(testArr)[0]
- yHat = zeros(m)
- for i in range(m):
- yHat[i] = lwlr(testArr[i], xArr, yArr, k)
- return yHat
-
- xArr, yArr = loadDataSet("ex0.txt")
- print "原始值:",yArr[0]
- print "估计值,k为1.0:",lwlr(xArr[0], xArr, yArr,1.0)
- print "估计值:k为0.001",lwlr(xArr[0], xArr, yArr,0.001)
-
- yHat = lwlrTest(xArr, xArr, yArr, 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()
效果:
Figure 8-5: LWLR对第一个点的估计
Figure 8-6: k分别为1.0, 0.01, 0.003时的模拟效果,上图欠拟合,下图则过拟合
缺点:LWLR增加了计算量,对每个点做预测时都必需使用整个数据集。在Figure 8-4图中,k=0.01时,对预测点x=0.5估计的时候,大多数据点的权重为0, 就不必用上整个数据集,避免增加计算量。
8.3 示例:预测鲍鱼的年龄
使用真实的数据观察k值对模型的效果,数据来自UCI数据集,预测鲍鱼的年龄。
Figure 8-7: 鲍鱼数据
-
- def rssError(yArr, yHatArr):
- return ((yArr - yHatArr)**2).sum()
-
-
- abX, abY = loadDataSet("abalone.txt")
- yHat01 = lwlrTest(abX[0:99], abX[0:99], abY[0:99], 0.1)
- yHat1 = lwlrTest(abX[0:99], abX[0:99], abY[0:99], 1)
- yHat10 = lwlrTest(abX[0:99], abX[0:99], abY[0:99], 10)
-
- print "k=0.1,训练集上的误差:",rssError(abY[0:99], yHat01.T)
- print "k=1, 训练集上的误差:",rssError(abY[0:99], yHat1.T)
- print "k=10, 训练集上的误差:",rssError(abY[0:99], yHat10.T)
-
- yHat01 = lwlrTest(abX[100:199], abX[0:99], abY[0:99], 0.1)
- yHat1 = lwlrTest(abX[100:199], abX[0:99], abY[0:99], 1)
- yHat10 = lwlrTest(abX[100:199], abX[0:99], abY[0:99], 10)
-
- print "k=0.1,测试集上的误差:",rssError(abY[100:199], yHat01.T)
- print "k=1, 测试集上的误差:",rssError(abY[100:199], yHat1.T)
- print "k=10, 测试集上的误差:",rssError(abY[100:199], yHat10.T)
-
- ws = standRegres(abX[0:99], abY[0:99])
- yHat = mat(abX[100:199])*ws
- print "简单线性回归上的误差和:",rssError(abY[100:199],yHat.T.A)
使用较小的核得到较低的训练误差,但会导致过拟合,也即对新的数据预测的效果比较糟糕。在简单线性回归中,达到了与局部加权线性回归类似的效果,在未知数据中,比较效果才能选取到最佳模型。
8.4 缩减系数来“理解”数据
当数据的特征比样本点还多时,就不能再用线性回归和LWLR,因为计算x.T*x的逆时会出错。为此引入岭回归(ridge regression)的概念。
岭回归
岭回归即在矩阵x.T*x上加一个λI从而使得矩阵非奇异,进而能对x.T*x+λI求逆,此时回归系数的计算公式变为:
岭回归最先用来处理特征数多于样本数的情况,在估计中加入偏差,从而得到更好的估计。引入λ来限制了所有w之和,通过引入该乘法项,能够减少不重要的参数,即缩减(shrinkage)技术。
缩减能够去掉不重要的参数,因此能够更好的理解数据,取得更好的预测效果。
使用岭回归和缩减技术,需要对特征做标准化处理,即所有特征都减去各自的均值并除以方差。
-
- 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"
- ws = linalg.pinv(denom)*(xMat.T*yMat)
- 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
- 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()
Figure 8-8: 岭回归参数与回归系数的关系
上图显示回归系数与log(λ)的关系,最左边λ最小时,8个系数的原始值与线性回归一样,最右边8个回归系数全部缩减为0,为找到最佳参数λ,还需要将ws乘以测试集比较原始测试集对比误差大小。0对应lam=exp(0-10),30对应lam=exp(30-10)
lasso
在增加约束:所有回归系数的平方和不能大于λ的条件下。普通的最小二乘法回归会得到与岭回归一样的公式。
使用普通的最小二乘法回归在两个或更多的特征相关时,可能会得出一个很大的正系数和一个很大的负系数。在上述约束中,可以约束避免这个问题。
缩减方法lasso也对回归系数做了约束:
在λ足够小的时候,一些系数会被迫缩减到0。约束条件为平方差的时候,可通过求偏导找到最优解,为绝对值形式时,需要使用二次规划,这里使用前向逐步回归。
前向逐步回归
每一步都尽可能减少误差,每一步所做的决策是对某个权重增加或减少一个很小的值。
伪代码:
coding:
-
- 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):
-
- 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
-
- xArr,yArr = loadDataSet("abalone.txt")
- print "步长为0.01,迭代次数为200:\n",stageWise(xArr, yArr, 0.01, 200)
- print "步长为0.001,迭代次数为5000:\n",stageWise(xArr, yArr, 0.001, 5000)
-
- xMat = mat(xArr)
- yMat = mat(yArr).T
- xMat = regularize(xMat)
- yM = mean(yMat, 0)
- yMat = yMat- yM
- weights = standRegres(xMat, yMat.T)
- print weights.T
Figure 8-9: 逐步线性回归迭代得到的回归系数
逐步线性回归算法可以帮助人们理解现有的模型并做出改进。构建了一个模型后,可以运行算法找出重要的特征,这样就有可能及时停止对那些不重要特征的收集。如上面步长0.01,迭代200次后为w1和w6都为0,表明它们对目标值没有影响,也即这些特征很可能是不需要的。
当应用缩减方法(逐步线性回归或岭回归)时,模型也就增加了偏差(bias),与此同时却减少了模型的方差。
8.5 权衡偏差与方差
方差:模型训练时,模型值之间的差异。
偏差:模型预测值与数据之间的差异。
通过缩减法,可以将一些系数缩减成很小的值或者为0,增大模型偏差,同时模型复杂度降低 。