機器學習實戰筆記(Python實現)-08-線性回歸


---------------------------------------------------------------------------------------

本系列文章為《機器學習實戰》學習筆記,內容整理自書本,網絡以及自己的理解,如有錯誤歡迎指正。

源碼在Python3.5上測試均通過,代碼及數據 --> https://github.com/Wellat/MLaction

---------------------------------------------------------------------------------------

1、線性回歸

現有一數據集,其分布如下圖所示,

通過觀察發現可以通過一個線性方程去擬合這些數據點。可設直線方程為 y=wx. 其中w稱為回歸系數。那么現在的問題是,如何從一堆x和對應的y中確定w?一個常用的方法就是找出使誤差最小的w。這里的誤差是指預測y值和真實y值之間的差值,我們采用平方誤差,寫作:

用矩陣還可以寫作: ,如果對w求導,得到,令其等於零,解出w為:

注意此處公式包含對矩陣求逆,所以求解時需要先對矩陣是否可逆做出判斷。以上求解w的過程也稱為“普通最小二乘法”。

Python實現代碼如下:

 1 from numpy import *
 2 
 3 def loadDataSet(fileName):
 4     '''導入數據'''
 5     numFeat = len(open(fileName).readline().split('\t')) - 1
 6     dataMat = []; labelMat = []
 7     fr = open(fileName)
 8     for line in fr.readlines():
 9         lineArr =[]
10         curLine = line.strip().split('\t')
11         for i in range(numFeat):
12             lineArr.append(float(curLine[i]))
13         dataMat.append(lineArr)
14         labelMat.append(float(curLine[-1]))
15     return dataMat,labelMat
16 
17 def standRegres(xArr,yArr):
18     '''求回歸系數'''
19     xMat = mat(xArr); yMat = mat(yArr).T
20     xTx = xMat.T*xMat
21     if linalg.det(xTx) == 0.0:#判斷行列式是否為0
22         print("This matrix is singular, cannot do inverse")
23         return
24     ws = xTx.I * (xMat.T*yMat)#也可以用NumPy庫的函數求解:ws=linalg.solve(xTx,xMat.T*yMatT)
25     return ws
26 
27 if __name__ == "__main__":
28     '''線性回歸'''
29     xArr,yArr=loadDataSet('ex0.txt')
30     ws=standRegres(xArr,yArr)
31     xMat=mat(xArr)
32     yMat=mat(yArr)
33     #預測值
34     yHat=xMat*ws
35     
36     #計算預測值和真實值得相關性
37     corrcoef(yHat.T,yMat)#0.986
38     
39     #繪制數據集散點圖和最佳擬合直線圖
40     #創建圖像並繪出原始的數據
41     import matplotlib.pyplot as plt
42     fig=plt.figure()
43     ax=fig.add_subplot(111)
44     ax.scatter(xMat[:,1].flatten().A[0],yMat.T[:,0].flatten().A[0])
45     #繪最佳擬合直線,需先要將點按照升序排列
46     xCopy=xMat.copy()
47     xCopy.sort(0)
48     yHat = xCopy*ws
49     ax.plot(xCopy[:,1],yHat)
50     plt.show()

幾乎任一數據集都可以用上述方法建立模型,只是需要判斷模型的好壞,計算預測值yHat和實際值yMat這兩個序列的相關系數,可以查看它們的匹配程度。

 

2、局部加權線性回歸

局部加權線性回歸給待預測點附近的每個點賦予一定的權重,用於解決線性回歸可能出現的欠擬合現象。與kNN法類似,這種算法每次預測均需要事先選取出對應的數據子集,然后在這個子集上基於最小均分差來進行普通的回歸。該算法解出回歸系數的形式如下:

其中w是一個權重矩陣,通常采用核函數來對附近的點賦予權重,最常用的核函數是高斯核,如下:

這樣就構建了一個只含對角元素的權重矩陣W並且點x與x(i)越近,w(i,i)將會越大,k值控制衰減速度,且k值越小被選用於訓練回歸模型的數據集越小。

Python實現代碼:

 1 def lwlr(testPoint,xArr,yArr,k=1.0):
 2     '''局部加權線性回歸函數'''
 3     xMat = mat(xArr); yMat = mat(yArr).T
 4     m = shape(xMat)[0]
 5     weights = mat(eye((m)))#創建對角矩陣
 6     for j in range(m):        
 7         diffMat = testPoint - xMat[j,:]
 8         #高斯核計算權重
 9         weights[j,j] = exp(diffMat*diffMat.T/(-2.0*k**2))
10     xTx = xMat.T * (weights * xMat)
11     if linalg.det(xTx) == 0.0:
12         print("This matrix is singular, cannot do inverse")
13         return
14     ws = xTx.I * (xMat.T * (weights * yMat))
15     return testPoint * ws
16 
17 def lwlrTest(testArr,xArr,yArr,k=1.0):
18     '''為數據集中每個點調用lwlr()'''
19     m = shape(testArr)[0]
20     yHat = zeros(m)
21     for i in range(m):
22         yHat[i] = lwlr(testArr[i],xArr,yArr,k)
23     return yHat
24 
25 if __name__ == "__main__":    
26     '''局部加權線性回歸'''
27     xArr,yArr=loadDataSet('ex0.txt')
28     #擬合
29     yHat=lwlrTest(xArr,xArr,yArr,0.01)
30     #繪圖
31     xMat=mat(xArr)
32     yMat=mat(yArr)
33     srtInd = xMat[:,1].argsort(0)
34     xSort=xMat[srtInd][:,0,:]    
35     import matplotlib.pyplot as plt
36     fig=plt.figure()
37     ax=fig.add_subplot(111)
38     ax.plot(xSort[:,1],yHat[srtInd])
39     ax.scatter(xMat[:,1].flatten().A[0],yMat.T[:,0].flatten().A[0],s=2,c='red')
40     plt.show()

k取0.01的結果

實際上,對k取不同值時有如下結果:

3、嶺回歸

如果數據的特征比樣本點多(n>m),也就是說輸入數據的矩陣x不是滿秩矩陣。而非滿秩矩陣在求逆時會出錯,所以此時不能使用之前的線性回歸方法。為解決這個問題,統計學家引入了嶺回歸的概念。

簡單來說,嶺回歸就是在矩陣xTx上加一個λI從而使得矩陣非奇異,進而能對 xTx+λI 求逆,其中I是一個mxm的單位矩陣。在這種情況下,回歸系數的計算公式將變成:

這里通過引入λ來限制了所有w之和,通過引入該懲罰項,能減少不重要的參數,這個技術在統計學中也叫縮減。

Python實現代碼:

 1 def ridgeRegres(xMat,yMat,lam=0.2):
 2     '''計算嶺回歸系數'''
 3     xTx = xMat.T*xMat
 4     denom = xTx + eye(shape(xMat)[1])*lam
 5     if linalg.det(denom) == 0.0:
 6         print("This matrix is singular, cannot do inverse")
 7         return
 8     ws = denom.I * (xMat.T*yMat)
 9     return ws
10     
11 def ridgeTest(xArr,yArr):
12     '''用於在一組lambda上測試結果'''
13     xMat = mat(xArr); yMat=mat(yArr).T
14     yMean = mean(yMat,0)
15     yMat = yMat - yMean     #數據標准化
16     xMeans = mean(xMat,0)   
17     xVar = var(xMat,0)      
18     xMat = (xMat - xMeans)/xVar #所有特征減去各自的均值並除以方差
19     numTestPts = 30 #取30個不同的lambda調用函數
20     wMat = zeros((numTestPts,shape(xMat)[1]))
21     for i in range(numTestPts):
22         ws = ridgeRegres(xMat,yMat,exp(i-10))
23         wMat[i,:]=ws.T
24     return wMat
25 
26 if __name__ == "__main__":
27     '''嶺回歸'''
28     abX,abY=loadDataSet('abalone.txt')
29     ridgeWeights = ridgeTest(abX,abY)#得到30組回歸系數
30     #縮減效果圖
31     import matplotlib.pyplot as plt
32     fig=plt.figure()
33     ax=fig.add_subplot(111)
34     ax.plot(ridgeWeights)
35     plt.show()   

 

運行之后得到下圖,橫軸表示第i組數據,縱軸表示該組數據對應的回歸系數值。從程序中可以看出lambda的取值為 exp(i-10) 其中i=0~29。所以結果圖的最左邊,即λ最小時,可以得到所有系數的原始值(與線性回歸一致);而在右邊,系數全部縮減為0;在中間部分的某些值可以取得最好的預測效果。

4、前向逐步回歸

前向逐步回歸算法屬於一種貪心算法,即每一步盡可能減少誤差。一開始,所有的權重都設為1,然后每一步所做的決策是對某個權重增加或減少一個很小的值。

該算法偽代碼如下所示:

Python實現代碼:

 

 1 def regularize(xMat):
 2     '''數據標准化函數'''
 3     inMat = xMat.copy()
 4     inMeans = mean(inMat,0)
 5     inVar = var(inMat,0)
 6     inMat = (inMat - inMeans)/inVar
 7     return inMat
 8     
 9 def rssError(yArr,yHatArr): 
10     '''計算均方誤差大小'''
11     return ((yArr-yHatArr)**2).sum()
12 
13 def stageWise(xArr,yArr,eps=0.01,numIt=100):
14     '''
15     逐步線性回歸算法
16     eps:表示每次迭代需要調整的步長
17     '''
18     xMat = mat(xArr); yMat=mat(yArr).T
19     yMean = mean(yMat,0)
20     yMat = yMat - yMean
21     xMat = regularize(xMat)
22     m,n=shape(xMat)
23     returnMat = zeros((numIt,n)) #testing code remove
24     #為了實現貪心算法建立ws的兩份副本
25     ws = zeros((n,1)); wsTest = ws.copy(); wsMax = ws.copy()
26     for i in range(numIt):
27         print(ws.T)
28         lowestError = inf;
29         for j in range(n):#對每個特征
30             for sign in [-1,1]:#分別計算增加或減少該特征對誤差的影響
31                 wsTest = ws.copy()
32                 wsTest[j] += eps*sign
33                 yTest = xMat*wsTest
34                 rssE = rssError(yMat.A,yTest.A)
35                 #取最小誤差
36                 if rssE < lowestError:
37                     lowestError = rssE
38                     wsMax = wsTest
39         ws = wsMax.copy()
40         returnMat[i,:]=ws.T
41     return returnMat
42 
43 if __name__ == "__main__":
44     '''前向逐步線性回歸'''    
45     abX,abY=loadDataSet('abalone.txt')
46     stageWise(abX,abY,0.01,200)

運行結果如下:

上述結果中值得注意的是w1和w6都是0,這表明它們不對目標值造成任何影響,也就是說這些特征很可能是不需要的。另外,第一個權重在0.04和0.05之間來回震盪,這是因為步長eps太大的緣故,一段時間后系數就已經飽和並在特定值之間來回震盪。  

5、實例:預測樂高玩具套裝的價格 

5.1 收集數據

原書介紹了從Google上在線獲取數據的方式,但是經測試該網址已經不可用,此處采用從離線網頁中爬取的方式收集數據。實現代碼如下:

 1 def setDataCollect(retX, retY):
 2     '''數據獲取方式一(不可用)'''
 3 #    searchForSet(retX, retY, 8288, 2006, 800, 49.99)
 4 #    searchForSet(retX, retY, 10030, 2002, 3096, 269.99)
 5 #    searchForSet(retX, retY, 10179, 2007, 5195, 499.99)
 6 #    searchForSet(retX, retY, 10181, 2007, 3428, 199.99)
 7 #    searchForSet(retX, retY, 10189, 2008, 5922, 299.99)
 8 #    searchForSet(retX, retY, 10196, 2009, 3263, 249.99)
 9     '''數據獲取方式二'''
10     scrapePage("setHtml/lego8288.html","data/lego8288.txt",2006, 800, 49.99)
11     scrapePage("setHtml/lego10030.html","data/lego10030.txt", 2002, 3096, 269.99)
12     scrapePage("setHtml/lego10179.html","data/lego10179.txt", 2007, 5195, 499.99)
13     scrapePage("setHtml/lego10181.html","data/lego10181.txt", 2007, 3428, 199.99)
14     scrapePage("setHtml/lego10189.html","data/lego10189.txt", 2008, 5922, 299.99)
15     scrapePage("setHtml/lego10196.html","data/lego10196.txt", 2009, 3263, 249.99)
16    
17 def scrapePage(inFile,outFile,yr,numPce,origPrc):
18     from bs4 import BeautifulSoup
19     fr = open(inFile,'r',encoding= 'utf8'); fw=open(outFile,'a') #a is append mode writing
20     soup = BeautifulSoup(fr.read())
21     i=1
22     currentRow = soup.findAll('table', r="%d" % i)
23     while(len(currentRow)!=0):
24         title = currentRow[0].findAll('a')[1].text
25         lwrTitle = title.lower()
26         if (lwrTitle.find('new') > -1) or (lwrTitle.find('nisb') > -1):
27             newFlag = 1.0
28         else:
29             newFlag = 0.0
30         soldUnicde = currentRow[0].findAll('td')[3].findAll('span')
31         if len(soldUnicde)==0:
32             print("item #%d did not sell" % i)
33         else:
34             soldPrice = currentRow[0].findAll('td')[4]
35             priceStr = soldPrice.text
36             priceStr = priceStr.replace('$','') #strips out $
37             priceStr = priceStr.replace(',','') #strips out ,
38             if len(soldPrice)>1:
39                 priceStr = priceStr.replace('Free shipping', '') #strips out Free Shipping
40             print("%s\t%d\t%s" % (priceStr,newFlag,title))
41             fw.write("%d\t%d\t%d\t%f\t%s\n" % (yr,numPce,newFlag,origPrc,priceStr))
42         i += 1
43         currentRow = soup.findAll('table', r="%d" % i)
44     fw.close()
45 
46 if __name__ == "__main__":
47     '''樂高玩具價格預測'''
48   #爬取數據
49   setDataCollect() 50 #讀取數據,這里已將以上方式獲取到的數據文本整合成為一個文件即legoAllData.txt 51 xmat,ymat = loadDataSet("data/legoAllData.txt")

 

5.2 訓練算法

首先我們用普通的線性回歸模型擬合數據看效果,擬合之前需要先添加對應常數項的特征X0

 1 if __name__ == "__main__":
 2     '''樂高玩具價格預測'''
 3     #爬取數據
 4 #    setDataCollect()
 5     #讀取數據,這里已將以上方式獲取到的數據文本整合成為一個文件即legoAllData.txt
 6 #    xMat,yMat = loadDataSet("data/legoAllData.txt")
 7     #添加對應常數項的特征X0(X0=1)
 8     lgX=mat(ones((76,5)))    
 9     lgX[:,1:5]=mat(xmat)
10     lgY=mat(ymat).T
11     
12     #用標准回歸方程擬合
13     ws1=standRegres(lgX,mat(ymat)) #求標准回歸系數
14     yHat = lgX*ws1 #預測值
15     err1 = rssError(lgY.A,yHat.A)   #計算平方誤差
16     cor1 = corrcoef(yHat.T,lgY.T) #計算預測值和真實值得相關性

測試結果為相關性cor1:0.7922,平方誤差和err1:3552526,顯然擬合效果還可以進一步提升。

接下來我們用交叉驗證測試嶺回歸:

 1 def crossValidation(xArr,yArr,numVal=10):
 2     '''
 3     交叉驗證測試嶺回歸
 4     numVal:交叉驗證次數
 5     '''    
 6     m = len(yArr)                           
 7     indexList = list(range(m))
 8     errorMat = zeros((numVal,30))
 9     for i in range(numVal):
10         trainX=[]; trainY=[]
11         testX = []; testY = []
12         random.shuffle(indexList)#打亂順序
13         for j in range(m):#構建訓練和測試數據,10%用於測試
14             if j < m*0.9: 
15                 trainX.append(xArr[indexList[j]])
16                 trainY.append(yArr[indexList[j]])
17             else:
18                 testX.append(xArr[indexList[j]])
19                 testY.append(yArr[indexList[j]])
20         wMat = ridgeTest(trainX,trainY)    #30組不同參數下的回歸系數集
21         for k in range(30):#遍歷30個回歸系數集
22             matTestX = mat(testX); matTrainX=mat(trainX)
23             meanTrain = mean(matTrainX,0)
24             varTrain = var(matTrainX,0)
25             matTestX = (matTestX-meanTrain)/varTrain #用訓練參數標准化測試數據
26             yEst = matTestX * mat(wMat[k,:]).T + mean(trainY)#預測值
27             errorMat[i,k]=rssError(yEst.T.A,array(testY))#計算預測平方誤差
28 #            print(errorMat[i,k])
29     #在完成所有交叉驗證后,errorMat保存了ridgeTest()每個lambda對應的多個誤差值
30     meanErrors = mean(errorMat,0)#計算每組平均誤差
31     minMean = float(min(meanErrors))
32     bestWeights = wMat[nonzero(meanErrors==minMean)]#平均誤差最小的組的回歸系數即為所求最佳
33     #嶺回歸使用了數據標准化,而strandRegres()則沒有,因此為了將上述比較可視化還需將數據還原    
34     xMat = mat(xArr); yMat=mat(yArr).T
35     meanX = mean(xMat,0); varX = var(xMat,0)
36     unReg = bestWeights/varX  #還原后的回歸系數
37     constant = -1*sum(multiply(meanX,unReg)) + mean(yMat) #常數項
38     print("the best model from Ridge Regression is:\n",unReg)
39     print("with constant term: ",constant)
40     return unReg,constant
41     
42     
43 if __name__ == "__main__":
44     '''樂高玩具價格預測'''
45     #用交叉驗證測試嶺回歸    
46     ws2,constant = crossValidation(xmat,ymat,10)    
47     yHat2 = mat(xmat)*ws2.T + constant
48     err2 = rssError(lgY.A,yHat2.A)
49     cor2 = corrcoef(yHat2.T,lgY.T)

測試結果為相關性cor2:0.7874,平方誤差和err2:3827083,與最小二乘法比較好並沒有太大差異。其實這種分析方法使得我們可以挖掘大量數據的內在規律。在僅有4個特征時,該方法的效果也許並不明顯;但如果有100個以上的特征,該方法就會變得十分有效:它可以指出哪些特征是關鍵的,而哪些特征是不重要的。 

 

 

 

THE END.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM