机器学习实战笔记(Python实现)-08-线性回归
---------------------------------------------------------------------------------------
本系列文章为《机器学习实战》学习笔记,内容整理自书本,网络以及自己的理解,如有错误欢迎指正。
源码在Python3.5上测试均通过,代码及数据 --> https://github.com/Wellat/MLaction
---------------------------------------------------------------------------------------
1、线性回归
现有一数据集,其分布如下图所示,
通过观察发现可以通过一个线性方程去拟合这些数据点。可设直线方程为 y=wx. 其中w称为回归系数。那么现在的问题是,如何从一堆x和对应的y中确定w?一个常用的方法就是找出使误差最小的w。这里的误差是指预测y值和真实y值之间的差值,我们采用平方误差,写作:
用矩阵还可以写作: ,如果对w求导,得到,令其等于零,解出w为:
注意此处公式包含对矩阵求逆,所以求解时需要先对矩阵是否可逆做出判断。以上求解w的过程也称为“普通最小二乘法”。
Python实现代码如下:
from numpy import * 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:#判断行列式是否为0
print("This matrix is singular, cannot do inverse")
return
ws = xTx.I * (xMat.T*yMat)#也可以用NumPy库的函数求解:ws=linalg.solve(xTx,xMat.T*yMatT)
return ws if __name__ == "__main__":
'''线性回归'''
xArr,yArr=loadDataSet('ex0.txt')
ws=standRegres(xArr,yArr)
xMat=mat(xArr)
yMat=mat(yArr)
#预测值
yHat=xMat*ws #计算预测值和真实值得相关性
corrcoef(yHat.T,yMat)#0.986 #绘制数据集散点图和最佳拟合直线图
#创建图像并绘出原始的数据
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()
xCopy.sort(0)
yHat = xCopy*ws
ax.plot(xCopy[:,1],yHat)
plt.show()
几乎任一数据集都可以用上述方法建立模型,只是需要判断模型的好坏,计算预测值yHat和实际值yMat这两个序列的相关系数,可以查看它们的匹配程度。
2、局部加权线性回归
局部加权线性回归给待预测点附近的每个点赋予一定的权重,用于解决线性回归可能出现的欠拟合现象。与kNN法类似,这种算法每次预测均需要事先选取出对应的数据子集,然后在这个子集上基于最小均分差来进行普通的回归。该算法解出回归系数的形式如下:
其中w是一个权重矩阵,通常采用核函数来对附近的点赋予权重,最常用的核函数是高斯核,如下:
这样就构建了一个只含对角元素的权重矩阵W并且点x与x(i)越近,w(i,i)将会越大,k值控制衰减速度,且k值越小被选用于训练回归模型的数据集越小。
Python实现代码:
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")
return
ws = xTx.I * (xMat.T * (weights * yMat))
return testPoint * ws def lwlrTest(testArr,xArr,yArr,k=1.0):
'''为数据集中每个点调用lwlr()'''
m = shape(testArr)[0]
yHat = zeros(m)
for i in range(m):
yHat[i] = lwlr(testArr[i],xArr,yArr,k)
return yHat if __name__ == "__main__":
'''局部加权线性回归'''
xArr,yArr=loadDataSet('ex0.txt')
#拟合
yHat=lwlrTest(xArr,xArr,yArr,0.01)
#绘图
xMat=mat(xArr)
yMat=mat(yArr)
srtInd = xMat[:,1].argsort(0)
xSort=xMat[srtInd][:,0,:]
import matplotlib.pyplot as plt
fig=plt.figure()
ax=fig.add_subplot(111)
ax.plot(xSort[:,1],yHat[srtInd])
ax.scatter(xMat[:,1].flatten().A[0],yMat.T[:,0].flatten().A[0],s=2,c='red')
plt.show()
k取0.01的结果
实际上,对k取不同值时有如下结果:
3、岭回归
如果数据的特征比样本点多(n>m),也就是说输入数据的矩阵x不是满秩矩阵。而非满秩矩阵在求逆时会出错,所以此时不能使用之前的线性回归方法。为解决这个问题,统计学家引入了岭回归的概念。
简单来说,岭回归就是在矩阵xTx上加一个λI从而使得矩阵非奇异,进而能对 xTx+λI 求逆,其中I是一个mxm的单位矩阵。在这种情况下,回归系数的计算公式将变成:
这里通过引入λ来限制了所有w之和,通过引入该惩罚项,能减少不重要的参数,这个技术在统计学中也叫缩减。
Python实现代码:
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):
'''用于在一组lambda上测试结果'''
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 #取30个不同的lambda调用函数
wMat = zeros((numTestPts,shape(xMat)[1]))
for i in range(numTestPts):
ws = ridgeRegres(xMat,yMat,exp(i-10))
wMat[i,:]=ws.T
return wMat if __name__ == "__main__":
'''岭回归'''
abX,abY=loadDataSet('abalone.txt')
ridgeWeights = ridgeTest(abX,abY)#得到30组回归系数
#缩减效果图
import matplotlib.pyplot as plt
fig=plt.figure()
ax=fig.add_subplot(111)
ax.plot(ridgeWeights)
plt.show()
运行之后得到下图,横轴表示第i组数据,纵轴表示该组数据对应的回归系数值。从程序中可以看出lambda的取值为 exp(i-10) 其中i=0~29。所以结果图的最左边,即λ最小时,可以得到所有系数的原始值(与线性回归一致);而在右边,系数全部缩减为0;在中间部分的某些值可以取得最好的预测效果。
4、前向逐步回归
前向逐步回归算法属于一种贪心算法,即每一步尽可能减少误差。一开始,所有的权重都设为1,然后每一步所做的决策是对某个权重增加或减少一个很小的值。
该算法伪代码如下所示:
Python实现代码:
def regularize(xMat):
'''数据标准化函数'''
inMat = xMat.copy()
inMeans = mean(inMat,0)
inVar = var(inMat,0)
inMat = (inMat - inMeans)/inVar
return inMat def rssError(yArr,yHatArr):
'''计算均方误差大小'''
return ((yArr-yHatArr)**2).sum() def stageWise(xArr,yArr,eps=0.01,numIt=100):
'''
逐步线性回归算法
eps:表示每次迭代需要调整的步长
'''
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)) #testing code remove
#为了实现贪心算法建立ws的两份副本
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 if __name__ == "__main__":
'''前向逐步线性回归'''
abX,abY=loadDataSet('abalone.txt')
stageWise(abX,abY,0.01,200)
运行结果如下:
上述结果中值得注意的是w1和w6都是0,这表明它们不对目标值造成任何影响,也就是说这些特征很可能是不需要的。另外,第一个权重在0.04和0.05之间来回震荡,这是因为步长eps太大的缘故,一段时间后系数就已经饱和并在特定值之间来回震荡。
5、实例:预测乐高玩具套装的价格
5.1 收集数据
原书介绍了从Google上在线获取数据的方式,但是经测试该网址已经不可用,此处采用从离线网页中爬取的方式收集数据。实现代码如下:
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)
'''数据获取方式二'''
scrapePage("setHtml/lego8288.html","data/lego8288.txt",2006, 800, 49.99)
scrapePage("setHtml/lego10030.html","data/lego10030.txt", 2002, 3096, 269.99)
scrapePage("setHtml/lego10179.html","data/lego10179.txt", 2007, 5195, 499.99)
scrapePage("setHtml/lego10181.html","data/lego10181.txt", 2007, 3428, 199.99)
scrapePage("setHtml/lego10189.html","data/lego10189.txt", 2008, 5922, 299.99)
scrapePage("setHtml/lego10196.html","data/lego10196.txt", 2009, 3263, 249.99) def scrapePage(inFile,outFile,yr,numPce,origPrc):
from bs4 import BeautifulSoup
fr = open(inFile,'r',encoding= 'utf8'); fw=open(outFile,'a') #a is append mode writing
soup = BeautifulSoup(fr.read())
i=1
currentRow = soup.findAll('table', r="%d" % i)
while(len(currentRow)!=0):
title = currentRow[0].findAll('a')[1].text
lwrTitle = title.lower()
if (lwrTitle.find('new') > -1) or (lwrTitle.find('nisb') > -1):
newFlag = 1.0
else:
newFlag = 0.0
soldUnicde = currentRow[0].findAll('td')[3].findAll('span')
if len(soldUnicde)==0:
print("item #%d did not sell" % i)
else:
soldPrice = currentRow[0].findAll('td')[4]
priceStr = soldPrice.text
priceStr = priceStr.replace('$','') #strips out $
priceStr = priceStr.replace(',','') #strips out ,
if len(soldPrice)>1:
priceStr = priceStr.replace('Free shipping', '') #strips out Free Shipping
print("%s\t%d\t%s" % (priceStr,newFlag,title))
fw.write("%d\t%d\t%d\t%f\t%s\n" % (yr,numPce,newFlag,origPrc,priceStr))
i += 1
currentRow = soup.findAll('table', r="%d" % i)
fw.close() if __name__ == "__main__":
'''乐高玩具价格预测'''
48 #爬取数据
49 setDataCollect()
50 #读取数据,这里已将以上方式获取到的数据文本整合成为一个文件即legoAllData.txt
51 xmat,ymat = loadDataSet("data/legoAllData.txt")
5.2 训练算法
首先我们用普通的线性回归模型拟合数据看效果,拟合之前需要先添加对应常数项的特征X0
if __name__ == "__main__":
'''乐高玩具价格预测'''
#爬取数据
# setDataCollect()
#读取数据,这里已将以上方式获取到的数据文本整合成为一个文件即legoAllData.txt
# xMat,yMat = loadDataSet("data/legoAllData.txt")
#添加对应常数项的特征X0(X0=1)
lgX=mat(ones((76,5)))
lgX[:,1:5]=mat(xmat)
lgY=mat(ymat).T #用标准回归方程拟合
ws1=standRegres(lgX,mat(ymat)) #求标准回归系数
yHat = lgX*ws1 #预测值
err1 = rssError(lgY.A,yHat.A) #计算平方误差
cor1 = corrcoef(yHat.T,lgY.T) #计算预测值和真实值得相关性
测试结果为相关性cor1:0.7922,平方误差和err1:3552526,显然拟合效果还可以进一步提升。
接下来我们用交叉验证测试岭回归:
def crossValidation(xArr,yArr,numVal=10):
'''
交叉验证测试岭回归
numVal:交叉验证次数
'''
m = len(yArr)
indexList = list(range(m))
errorMat = zeros((numVal,30))
for i in range(numVal):
trainX=[]; trainY=[]
testX = []; testY = []
random.shuffle(indexList)#打乱顺序
for j in range(m):#构建训练和测试数据,10%用于测试
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) #30组不同参数下的回归系数集
for k in range(30):#遍历30个回归系数集
matTestX = mat(testX); matTrainX=mat(trainX)
meanTrain = mean(matTrainX,0)
varTrain = var(matTrainX,0)
matTestX = (matTestX-meanTrain)/varTrain #用训练参数标准化测试数据
yEst = matTestX * mat(wMat[k,:]).T + mean(trainY)#预测值
errorMat[i,k]=rssError(yEst.T.A,array(testY))#计算预测平方误差
# print(errorMat[i,k])
#在完成所有交叉验证后,errorMat保存了ridgeTest()每个lambda对应的多个误差值
meanErrors = mean(errorMat,0)#计算每组平均误差
minMean = float(min(meanErrors))
bestWeights = wMat[nonzero(meanErrors==minMean)]#平均误差最小的组的回归系数即为所求最佳
#岭回归使用了数据标准化,而strandRegres()则没有,因此为了将上述比较可视化还需将数据还原
xMat = mat(xArr); yMat=mat(yArr).T
meanX = mean(xMat,0); varX = var(xMat,0)
unReg = bestWeights/varX #还原后的回归系数
constant = -1*sum(multiply(meanX,unReg)) + mean(yMat) #常数项
print("the best model from Ridge Regression is:\n",unReg)
print("with constant term: ",constant)
return unReg,constant if __name__ == "__main__":
'''乐高玩具价格预测'''
#用交叉验证测试岭回归
ws2,constant = crossValidation(xmat,ymat,10)
yHat2 = mat(xmat)*ws2.T + constant
err2 = rssError(lgY.A,yHat2.A)
cor2 = corrcoef(yHat2.T,lgY.T)
测试结果为相关性cor2:0.7874,平方误差和err2:3827083,与最小二乘法比较好并没有太大差异。其实这种分析方法使得我们可以挖掘大量数据的内在规律。在仅有4个特征时,该方法的效果也许并不明显;但如果有100个以上的特征,该方法就会变得十分有效:它可以指出哪些特征是关键的,而哪些特征是不重要的。
THE END.
机器学习实战笔记(Python实现)-08-线性回归的更多相关文章
- 机器学习实战笔记(Python实现)-09-树回归
---------------------------------------------------------------------------------------- 本系列文章为<机 ...
- 机器学习实战笔记(Python实现)-05-支持向量机(SVM)
--------------------------------------------------------------------------------------- 本系列文章为<机器 ...
- 机器学习实战笔记(Python实现)-04-Logistic回归
--------------------------------------------------------------------------------------- 本系列文章为<机器 ...
- 机器学习实战笔记(Python实现)-03-朴素贝叶斯
--------------------------------------------------------------------------------------- 本系列文章为<机器 ...
- 机器学习实战笔记(Python实现)-01-K近邻算法(KNN)
--------------------------------------------------------------------------------------- 本系列文章为<机器 ...
- 机器学习实战笔记(Python实现)-02-决策树
--------------------------------------------------------------------------------------- 本系列文章为<机器 ...
- 机器学习实战笔记(Python实现)-06-AdaBoost
--------------------------------------------------------------------------------------- 本系列文章为<机器 ...
- 机器学习实战笔记(Python实现)-00-readme
近期学习机器学习,找到一本不错的教材<机器学习实战>.特此做这份学习笔记,以供日后翻阅. 机器学习算法分为有监督学习和无监督学习.这本书前两部分介绍的是有监督学习,第三部分介绍的是无监督学 ...
- 机器学习实战笔记(Python实现)-07-模型评估与分类性能度量
1.经验误差与过拟合 通常我们把分类错误的样本数占样本总数的比例称为“错误率”(error rate),即如果在m个样本中有a个样本分类错误,则错误率E=a/m:相应的,1-a/m称为“精度”(acc ...
随机推荐
- 镜像切换Logreader Agent报错:分发数据库中可能存在不一致的状态(续)
报错: 分发数据库中可能存在不一致的状态: dist_backup_lsn {00000030:000001ba:0004},dist_last_lsn {00000030:000001cd:0004 ...
- HTML5 & CSS3初学者指南(2) – 样式化第一个网页
介绍 我们已经使用基本的 HTML 编写了一个网页.但是,写出来的 HTML 代码的网页看起来很平淡,没有吸引力. 如何改善这种很平淡的页面呢? 让我们开始使用网页的基本样式来改善页面效果,我们将会使 ...
- ABP框架 - 日志
文档目录 本节内容: 服务端 获取Logger(记录器) Logger的基类 配置 Abp.Castle.Log4Net 包 客户端 服务端 ABP使用Castle Windsor的日志记录工具,它可 ...
- 空中网招聘Java架构师、数据库开发等各类人才
爱好网络游戏吗?爱好网站开发技术吗? 有没有想过可以成为史诗级MMO RPG<激战2>运营团队中的一员? 如果下面的职位有合适你的,加入我们吧! http://gw2.kongzhong. ...
- CI Weekly #7 | Instgram/Quora 等大公司如何做持续部署?
终于,你们期待的 flow.ci iOS 项目持续集成 开始公测了.在这几个工作日, flow.ci 做了些许「功能优化」与「问题修复」,性能和体验都在持续优化中.比如: iOS 快速入门文档更新: ...
- 【转】java.util.Arrays.asList 的用法
DK 1.4对java.util.Arrays.asList的定义,函数参数是Object[].所以,在1.4中asList()并不支持基本类型的数组作参数. JDK 1.5中,java.util.A ...
- 编程之美—烙饼排序问题(JAVA)
一.问题描述 星期五的晚上,一帮同事在希格玛大厦附近的"硬盘酒吧"多喝了几杯.程序员多喝了几杯之后谈什么呢?自然是算法问题.有个同事说:"我以前在餐 馆打工,顾 ...
- EXISTS 引入子查询时,在选择列表中只能指定一个表达式
- JavaScript的闭包
1. 什么是闭包 通俗地讲,JavaScript 中每个的函数都是一个闭包,但通常意义上嵌套的函数更能够体现出闭包的特性,请看下面这个例子: var generateClosure = functio ...
- 真实记录疑似Linux病毒导致服务器 带宽跑满的解决过程
案例描述 由于最近我在重构之前的APP,需要和server端进行数据交互,发现有一个现象,那么就是隔1~2天总会发生获取数据超时的问题,而且必须要重启服务器才能解决.早在之前,我有留意到这个问题,但是 ...