回归是前述监督学习方法的延续。
监督学习指的是有目标变量或预测目标的机器学习方法。回归与分类的不同,就在于其目标变量是连续数值型
8.1 用线性回归找到最佳拟合直线
- 线性回归优缺点:
优点:结果易于理解,计算上不复杂
缺点:对非线性数据拟合不好 - 回归的目的是预测数值型的目标值。
最直接的方法是依据输入写出一个目标值的计算公式。这就是所谓的回归方程(regression equation).方程中的常数为回归系数,求这些系数的过程就是回归。(注意区分logistic回归与本章的回归的区别) - 回归的一般方法
1)收集数据
2)准备数据:回归需要数值型数据,标称型数据将被转成二值型数据
3)分析数据:给出数据的可视化二维图将有助于对数据做出理解和分析,在采用缩减法求得新回归洗漱之后,可以将新拟合线绘在图上作为对比
4)训练算法:找到回归系数
5)测试算法:使用R^2或者预测值和数据的拟合度,来分析模型的结果
6)使用算法:使用回归,可以在给定输入的时候预测出一个数值,这是对分类方法的提升,因为这样可以预测连续型数据而不仅仅的离散的类别标签。
假定数据存放在矩阵X中,而回归系数存放在向量W中。对于戈丁的数据X1,预测结果将会通过Y1 = X1^2W给出。怎么找到这个W呢?一个常用的方法就是找出使平方误差最小的W。
在logistic回归中,我们使用平方误差,然后寻找平方误差梯度的最大方向来更新alpha回归系数
在线性回归中,对平方误差关于回归系数求导置0,取得系数值。得W = (XTX)-1X^Ty.对W的求解,除了矩阵方法,还有牛顿最小二乘法,后者迭代次数更少。
两者的求解回归系数过程并没什么不同,最终收敛点都是导数为0或接近于0的点。Logistic回归接近的可能是一个局部最小,而线性回归是全局最小。最终两个的假设函数不同,logistic回归使用的是sigmoid函数,而线性回归使用的是线性函数Y1 = X1^2W。最重要的一点是一个针对的是离散型数据,一个是连续型数据 - 如何比较模型的拟合效果-相关系数
计算预测值yHat序列和真实值y序列的匹配程度,可通过计算两个序列的相关系数实现
np.corrcoef(yHat.T,yMat)
"""标注回归函数和数据导入函数"""
import numpy as np
import matplotlib.pyplot as plt
from numpy.ma import exp
#标准线性回归算法
def loadDataSet(filename):
numFeat = len(open(filename).readline().split('\t')) - 1
dataArr = []
labelArr = []
fr = open(filename)
for line in fr.readlines():
lineArr = []
curline = line.strip().split('\t')
for i in range(numFeat):
lineArr.append(float(curline[i]))
dataArr.append(lineArr)
labelArr.append(float(curline[-1]))
return dataArr,labelArr
def standRegress(xArr,yArr):
"""标准回归函数:W = (X^TX)^-1X^Ty"""
xMat = np.mat(xArr)
yMat = np.mat(yArr)
xTx = xMat.T*xMat
#判断矩阵是否满秩,矩阵求逆
if np.linalg.det(xTx) == 0: #np.linalg.det(xTx)行列式
print("this matrix is singular,cannot do inverse")
return
weights = xTx.I * (xMat.T*yMat.T) #xTx.I矩阵求逆
return weights
def plotLine(xArr,yArr,weights):
"""绘制拟合直线"""
weights = np.mat(weights)
xMat = np.mat(xArr)
yMat = np.mat(yArr)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xMat[:,1].flatten().A[0],yMat.T[:,0].flatten().A[0])
#上述命令创建了图像并绘出了原始的数据。为了绘制计算出的最佳拟合直线,需要绘出yHat的值。如果直线上的数据点次序混乱,绘图时将会出现问题,所以首先要将点按升序排列
xCopy = xMat.copy()
xCopy.sort(0)
yHat = xCopy*weights
ax.plot(xCopy[:,1],yHat)
plt.show()
return yHat,yMat
def coef(yHat,yMat):
coefResult = np.corrcoef(yHat.T,yMat)
return coefResult
8.2 局部加权线性回归
线性回归的一个问题是有可能出现欠拟合现象,因为它求的是具有最小均方误差的无偏估计。
- 无偏估计
设总体ξ的概率分布函数为F(x;θ),其中x为变元,θ∈Θ为未知参数,Θ称为参数空间。(ξ1,ξ2,…,ξn)为取自总体ξ的随机样本,若 (ξ1,…,ξn)是参数θ的一个估计量,且对一列θ∈Θ关系式Eθ(θ’(ξ1,…,ξn))=θ成立,其中,E[θ’(ξ1,ξ2,…,ξn)]表示数学期望,则称 (ξ1…ξn)为θ的无偏估计,并称 具有无偏性。 - 局部加权线性回归(locally weighted linear regression,LWLR)
在该算法中,我们给待预测点附近的每个点赋予一定的权重,然后在这个子集上基于最小均方差来进行普通的回归。与KNN一样,这种算法每次预测均需要事先选取出对应的数据子集。该算法解出回归系数w的形式如下:
W = (X.TWX).I*X.TWy 其中W是一个矩阵,用来给每个数据点赋予权重
LWLR使用"核"(与支持向量机中的核类似)来对附近的点赋予更高的权重。核的类型可以自由选择,最常用的核就是高斯核。
#局部加权线性回归算法
#局部加权线性回归建立模型,可以得到比普通线性回归更好的效果
#问题在于:LWLR每次必须在整个数据集上运行,也就是说为了做出预测,必须保存所有的训练数据
def lwlr(testPoint,xArr,yArr,k = 1.0):
"""局部加权线性回归函数:W = (X.TWX).I*X.TWy
给定X空间中的任意一点,计算出对应的预测值yHat
随着样本点与待预测点距离的递增,权重将以指数级衰减。参数k控制衰减的速度。"""
xMat = np.mat(xArr)
yMat = np.mat(yArr).T
m = np.shape(xMat)[0]
weights = np.mat(np.eye((m))) #生成对角单位矩阵
for j in range(m):
diffMat = testPoint - xMat[j,:]
weights[j,j] = exp(diffMat*diffMat.T/(-2.0*k**2)) #高斯函数,随着样本点与待预测点距离的递增,权重将以指数级衰减。参数k控制衰减的速度。
xTx = xMat.T*(weights*xMat)
if np.linalg.det(xTx) == 0.0:
print("this matrix is singular,cannot do inverse")
return
ws = xTx.T * (xMat.T*(weights*yMat))
return ws
def lwlrTest(testArr,xArr,yArr,k=1.0):
m = np.shape(testArr)[0]
yHat = np.zeros(m)
for i in range(m):
ws = lwlr(testArr[i],xArr,yArr,k)
yHat[i] = testArr[i]*ws
return yHat
def rssError(yArr,yHatArr):
sumError = ((yArr - yHatArr)**2).sum()
errorRate = sumError/len(yArr)
return errorRate
8.3 缩减系数来“理解”数据
如果数据的特征比样本点还多怎么办?
8.4.1 岭回归
- 简单来说,岭回归就是在矩阵X.TX上加上一个CI从而使矩阵非奇异,进而能对X.TX+CI求逆。矩阵I是一个mxm的单位矩阵。C是一个用户定义的数值。C=lambda
最终:w = (X.TX+CI).IX.Ty
通过引入C来限制了所有W之和,通过引入该惩罚项,能够减少不重要的参数,这就是“缩减(shrinkage)” - 在增加如下约束时,普通的最小二乘法回归会得到与岭回归一样的公式。
Σ(Wi)^2 <= lambda i={1-n},n为特征数 - 如何得到lambda?
预测误差最小化得到:数据获取之后,首先抽一部分数据用于测试,剩余的作为训练集用于训练参数W。训练完毕后在测试集上测试预测性能。通过选取不同的C来重复上述测试过程,最终得到一个使预测误差最小的C。
#岭回归
#w = (X.TX+CI).IX.Ty
def ridgeRegres(xMat,yMat,lam=0.2):
"""给定lambda下的岭回归求解"""
xTx = xMat.T*xMat
denom = xTx + np.eye(np.shape(xMat)[1])*lam #X.TX+CI
if np.linalg.det(denom) == 0.0: #如果lambda设置为0,仍可能出现错误,故仍需判断行列式
print('this matrix is singular,cannot do inverse')
return
ws = denom.I * (xMat.T*yMat) #w = (X.TX+CI).IX.Ty
return ws
def ridgeTest(xArr,yArr):
"""对特征进行标准化处理,使每维特征具有相同的重要性"""
xMat = np.mat(xArr)
yMat = np.mat(yArr).T
#数据标准化:所有特征都减去各自均值并除以方差
yMean = np.mean(yMat,0)
yMat = yMat - yMean
xMeans = np.mean(xMat,0)
print(xMeans)
xVar = np.var(xMat,0)
xMat = (xMat - xMeans)/xVar
numTestPts = 30 #30个不同的lambda
wMat = np.zeros((numTestPts,np.shape(xMat)[1]))
for i in range(numTestPts):
ws = ridgeRegres(xMat,yMat,exp(i-10)) #lambda以指数变化
wMat[i,:] = ws.T
return wMat
def regularize(xMat):
"""标准化处理"""
xMean = np.mean(xMat,0)
xMat = xMat - xMean
xVar = np.var(xMat,0)
returnMat = xMat/xVar
return returnMat
8.4.2 lasso
与岭回归类似,lasso也对回归系数做了限定:
Σ |Wi| <= lambda
在lambda足够小的时候,一些系数会因此被迫缩减到0
8.4.3 前向逐步回归
- 前向逐步回归是一种贪心算法,即每一步都尽可能减少误差,一开始,所有的权重都设为1,然后每一步所做的决策是对某个权重增加或减少一个很小的值。
- 该算法的伪代码:
数据标准化,使其分布满足0均值和单位方差
在每轮迭代过程中:
设置当前最小误差lowestError为正无穷
对每个特征:
增大或缩小:
改变一个系数得到一个新的W
计算新W下的误差
如果五擦汗Error小于当前最小误差lowestError:
设置Wbest等于当前的W
将W设置为新的Wbest - 当构建了一个模型后,可以运行该算法找出重要的特征(不重要的特征,算法会给出权重为0)
当应用了缩减方法(如逐步线性回归或岭回归)时,模型也就增加了偏差(bias),与此同时却减小了模型的方差。
#前向逐步回归
def stageWise(xArr,yArr,eps=0.01,numIt=100):
"""前向逐步回归:输入数据xArr,预测变量yArr,每次迭代需要调整的步长eps,迭代次数numIt\n
贪心算法在所有特征上运行两次for循环,分别计算增加或减少该特征对误差的影响。
在参数设置为0.01的情况下,一段时间后系数就已经饱和并在特定值之间来回震荡,这是由于步长eps太大的缘故。
"""
xMat = np.mat(xArr)
yMat = np.mat(yArr).T
yMean = np.mean(yMat,0)
yMat = yMat - yMean
xMat = regularize(xMat) #按照均值为0方差为1进行标准化处理
m,n = np.shape(xMat)
returnMat = np.zeros((numIt,n))
ws = np.zeros((n,1))
wsTest = ws.copy()
wsMax = ws.copy()
for i in range(numIt):
print(ws.T)
lowestError = np.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
if __name__ == '__main__':
#测试普通线性回归
# xArr,yArr = loadDataSet('./M_08_回归/ex0.txt')
# weights = standRegress(xArr,yArr)
# print('==============')
# print(weights)
# yHat,yMat = plotLine(xArr,yArr,weights)
# print(coef(yHat,yMat))
#预测鲍鱼的年龄
abX,abY = loadDataSet('./M_08_回归/abalone.txt')
#不同的K值
# 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)
# errorRate01 = rssError(abY[0:99],yHat01.T)
# errorRate1 = rssError(abY[0:99],yHat1.T)
# errorRate10 = rssError(abY[0:99],yHat10.T)
#前向逐步回归
returnMat = stageWise(abX,abY,0.01,200)
print(returnMat)
8.5 权衡偏差与方差
两组随机样本集拟合得到两组不同的回归系数,这些系数间的差异大小也就是模型方差大小的反映。
8.6 示例:预测乐高玩具套装的价格
用回归法预测乐高套装的价格
1.收集数据:用google shopping的API收集数据
2.准备数据:从返回的json数据中抽取价格
3.分析数据:可视化并观察数据
4.训练算法:采用不同的模型,采用逐步线性回归和直接的线性回归模型
5.测试算法:使用交叉验证来测试不同的模型,分析哪个效果最好
6.使用算法:生成数据模型
import numpy as np
import random
from time import sleep
import json
from urllib.request import urlopen
from numpy.ma import exp
def searchForSet(retX, retY, setNum, yr, numPce, origPrc):
sleep(10)
myAPIstr = 'AIzaSyD2cR2KFyx12hXu6PFU-wrWot3NXvko8vY'
searchURL = 'https://www.googleapis.com/shopping/search/v1/public/products?key=%s&country=US&q=lego+%d&alt=json' % (myAPIstr, setNum)
pg = urlopen(searchURL)
retDict = json.loads(pg.read())
for i in range(len(retDict['items'])):
try:
currItem = retDict['items'][i]
if currItem['product']['condition'] == 'new':
newFlag = 1
else: newFlag = 0
listOfInv = currItem['product']['inventories']
for item in listOfInv:
sellingPrice = item['price']
if sellingPrice > origPrc * 0.5:
print ("%d\t%d\t%d\t%f\t%f" % (yr,numPce,newFlag,origPrc, sellingPrice))
retX.append([yr, numPce, newFlag, origPrc])
retY.append(sellingPrice)
except: print ('problem with item %d' % i)
def setDataCollect(retX, retY):
searchForSet(retX, retY, 8288, 2006, 800, 49.99)
searchForSet(retX, retY, 10030, 2002, 3096, 269.99)
searchForSet(retX, retY, 10179, 2007, 5195, 499.99)
searchForSet(retX, retY, 10181, 2007, 3428, 199.99)
searchForSet(retX, retY, 10189, 2008, 5922, 299.99)
searchForSet(retX, retY, 10196, 2009, 3263, 249.99)
def rssError(yArr,yHatArr):
sumError = ((yArr - yHatArr)**2).sum()
errorRate = sumError/len(yArr)
return errorRate
#岭回归
def ridgeRegres(xMat,yMat,lam=0.2):
"""给定lambda下的岭回归求解"""
xTx = xMat.T*xMat
denom = xTx + np.eye(np.shape(xMat)[1])*lam #X.TX+CI
if np.linalg.det(denom) == 0.0: #如果lambda设置为0,仍可能出现错误,故仍需判断行列式
print('this matrix is singular,cannot do inverse')
return
ws = denom.I * (xMat.T*yMat) #w = (X.TX+CI).IX.Ty
return ws
def ridgeTest(xArr,yArr):
"""对特征进行标准化处理,使每维特征具有相同的重要性"""
xMat = np.mat(xArr)
yMat = np.mat(yArr).T
#数据标准化:所有特征都减去各自均值并除以方差
yMean = np.mean(yMat,0)
yMat = yMat - yMean
xMeans = np.mean(xMat,0)
print(xMeans)
xVar = np.var(xMat,0)
xMat = (xMat - xMeans)/xVar
numTestPts = 30 #30个不同的lambda
wMat = np.zeros((numTestPts,np.shape(xMat)[1]))
for i in range(numTestPts):
ws = ridgeRegres(xMat,yMat,exp(i-10)) #lambda以指数变化
wMat[i,:] = ws.T
return wMat
def crossValidation(xArr,yArr,numVal = 10):
"""numVal:交叉验证的次数"""
m = len(yArr)
indexList = range(m)
errorMat = np.zeros((numVal,30))
for i in range(numVal):
trainX = []
trainY = []
testX = []
testY = []
random.shuffle(indexList) #实现训练和测试数据的随机抽取
#分为训练集和测试集
for j in range(m):
if j < m*0.9:
trainX.append(xArr[indexList[j]])
trainY.append(yArr[indexList[j]])
else:
testX.append(xArr[indexList[j]])
testY.append(yArr[indexList[j]])
wMat = ridgeTest(trainX,trainY)
for k in range(30):
#用训练时的参数将测试数据标准化
matTestX = np.mat(testX)
matTrainX = np.mat(trainX)
meanTrain = np.mean(matTrainX,0)
varTrain = np.var(matTrainX,0)
matTestX = (matTestX - meanTrain)/varTrain
yEst = matTestX*np.mat(wMat[k,:]).T+np.mean(trainY)
errorMat[i,k] = rssError(yEst.T.A,np.array(testY))
meanErrors = np.mean(errorMat,0)
minMean = float(min(meanErrors))
bestWeights = wMat[np.nonzero(meanErrors==minMean)]
print("the best regression params :",bestWeights)
xMat = np.mat(xArr)
yMat = np.mat(yArr).T
meanX = np.mean(xMat,0)
varX = np.var(xMat,0)
#数据还原-便于与其他的算法模型拟合结果进行比较
unReg = bestWeights/varX
print('the best model from Ridge Regression is:\n',unReg)
print('with constant term',-1*sum(np.multiply(meanX,unReg))+np.mean(yMat))
if __name__ == '__main__':
lgX = []; lgY = []
setDataCollect(lgX,lgY)
#需要添加对应常数项的特征X0(X0=1)
m,n = np.shape(lgX)
print(m)
lgX1 = np.mat(np.ones(m,n+1))
lgX1[:,1:5] = np.mat(lgX)
8.7 小结
在回归方程中,求得特征对应的最佳回归系数的方法是最小化误差的平方和
给定输入矩阵X,如果X.TX的逆存在,回归法都可以直接使用
数据集上计算出的回归方程并不一定意味着它是最佳的,可以使用预测值yHat和原始值y的相关性来度量回归方程的好坏
岭回归是缩减法的一种,相当于对回归系数的大小施加了限制。另一种很好的缩减法是lasso.lasso难以求解,但可以使用计算简便的逐步线性回归方法来求得近似结果
缩减法可以看作是对一个模型增加偏差的同时减小方差的一种方法。