8. 1 用線性回歸找到最佳擬合直線
線性回歸
優點:結果易於理解,計算上不復雜。
缺點:對非線性的數據擬合不好。
適用數據類型:數值型和標稱型數據。
回歸的目的是預測數值型的目標值。最直接的辦法是依據輸人寫出一個目標值的計算公式。
假如你想要預測姐姐男友汽車的功率大小,可能會這么計算:
HorsePower = 0.0015* annualSalary - 0.99* hoursListeningToPublicRadio
這就是所謂的回歸方程(regression equation),其中的0.0015和-0.99稱作回歸系數(regression weights) ,求這些回歸系數的過程就是回歸。一旦有了這些回歸系數,再給定輸人,做預測就非常容易了。具體的做法是用回歸系數乘以輸人值,再將結果全部加在一起,就得到了預測值。
回歸的一般方法
(1)收集數據:采用任意方法收集數據。
(2)准備數據:回歸需要數值型數據,標稱型數據將被轉成二值型數據。
(3)分析數據:繪出數據的可視化二維圖將有助於對數據做出理解和分析,在采用縮減法求得新回歸系數之后, 可以將新擬合線繪在圖上作為對比。
(4)訓練算法:找到回歸系數。
(5)測試算法:使用R2或者預測值和數據的擬合度,來分析模型的效果。
(6)使用算法:使用回歸,可以在給定輸入的時候預測出一個數值,這是對分類方法的提升,因為這樣可以預測連續型數據而不僅僅是離散的類別標簽。
應當怎樣從一大堆數據里求出回歸方程呢?假定輸人數據存放在矩陣X中,而回歸系數存放在向量w中。那么對於給定的數據x1 , 預測結果將會通過Y1=XT1w給出。現在的問題是,手里有一些x和對應的y,怎樣才能找到w呢?一個常用的方法就是找出使誤差最小的w,這里的誤差是指預測值和真實值之間的差值,使用該誤差的簡單累加將使得正差值和負差值相互抵消,所以我們采用平方誤差。
平方誤差可以寫作:
用矩陣表示還可以寫做(y−xw)T(y−xw)。如果對w求導,得到XT(Y−Xw),令其等於零,解出w如下:
w上方的小標記表示,這是當前可以估計出的w最優解。從現有數據上估計出的w可能並不是數據中的真實城值,所以這里使用了一個“帽”符號來表示它僅是w的一個最佳估計。(不知道怎么把冒號添上去,汗)
值得注意的是,上述公式中包含(XTX−1), 也就是需要對矩陣求逆,因此這個方程只在逆矩陣存在的時候適用。然而,矩陣的逆可能並不存在,因此必須要在代碼中對此作出判斷。
上述的最佳w求解是統計學中的常見問題,除了矩陣方法外還有很多其他方法可以解決。通過調用沖Numpy庫里的矩陣方法,我們可以僅使用幾行代碼就完成所需功熊。該方法也稱作OLS,意 思是”普通最小二乘法”(ordinary least squares)。
下面看看實際效果,對於圖8-1中的散點圖,下面來介紹如何給出該數據的最佳擬合直線。
標准回歸函數和數據導人函數,代碼如下所示:
#general function to parse tab -delimited floats def loadDataSet(fileName): #get number of fields,默認最后一列為y 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 #防止xTx無法求逆矩陣 if linalg.det(xTx) == 0.0: print "This matrix is singular, cannot do inverse" return #利用之前推導出的公式求出w ws = xTx.I * (xMat.T*yMat) return ws
測試截圖如下:
變量ws存放的就是回歸系數。在用內積來預測y的時候,第一維將乘以前面的常數X0,第二維將乘以輸人變量X1。因為前面假定了X0=1,所以最終會得到y =ws[0]+ws [1]*X1。這里的y實際是預測出的,為了和真實的y值區分開來,我們將它記為yHat 。下面使用新的ws值計算yHat:
注意,如果直線上的數據點次序混亂,繪圖時將會出現問題,所以首先要將點按照升序排列,使用sort方法。
幾乎任一數據集都可以用上述方法建立模型,那么,如何判斷這些模型的好壞呢?比較一下圖8-3的兩個子圖,如果在兩個數據集上分別作線性回歸,將得到完全一樣的模型(擬合直線)。顯然兩個數據是不一樣的,那么模型分別在二者上的效果如何?我們當如何比較這些效果的好壞呢?有種方法可以計算預測值沖社序列和真實值乂序列的匹配程度,男防尤是計算這兩個序列的相關系數。
在python中 ,Numpy庫提供了相關系數的計算方法:可以通過命令corrcoef(yEstimate,yActual)來計算預測值和真實值的相關性。下面我們就在前面的數據集上做個實驗。
該矩陣包含所有兩兩組合的相關系數。可以看到,對角線上的數據是1.0,因為yMat和自己的匹配是最完美的,而YHat和YMat的相關系數為0.98。
8.2 局部加權線性回歸
線性回歸的一個問題是有可能出現欠擬合現象,因為它求的是具有最小均方誤差的無偏估計 。顯而易見,如果模型欠擬合將不能取得最好的預測效果。所以有些方法允許在估計中引人一些偏差,從而降低預測的均方誤差。
其中的一個方法是局部加權線性回歸(Locally Weighted Linear Regression, LWLR )。在該算法中 ,我們給待預測點附近的每個點賦予一定的權重;然后與8.1節類似,在這個子集上基於最小均方差來進行普通的回歸。與kNN一樣, 這種算法每次預測均需要事先選取出對應的數據子集。該算法解出回歸系數w的形式如下:
其中w是一個矩陣,用來給每個數據點賦予權重。
LWLR使用”核”(與支持向量機中的核類似)來對附近的點賦予更高的權重。核的類型可以自由選擇,最常用的核就是高斯核,高斯核對應的權重如下:
這樣就構建了一個只含對角元素的權重矩陣w並且點x與x(i)越近,w(i,i)將會越 大 。上述公式包含一個需要用戶指定的參數k,它決定了對附近的點賦予多大的權重,這也是使用LWLR時唯一需要考慮的參數,在圖8-4中可以看到參數k與權重的關系。
局部加權線性回歸函數,代碼如下:
def lwlr(testPoint,xArr,yArr,k=1.0): #將二維數組xMat, yMat轉化為矩陣 xMat = mat(xArr); yMat = mat(yArr).T m = shape(xMat)[0] #加權矩陣,初始化為m維單位矩陣 weights = mat(eye((m))) for j in range(m): #next 2 lines create weights matrix #循環求出testPoint與矩陣中每行向量的差 diffMat = testPoint - xMat[j,:] #利用權重公式求出testPoint對應的此矩陣的權重 weights[j,j] = exp(diffMat*diffMat.T/(-2.0*k**2)) #依然利用上節的公式求出xTx,不同的是對xMat加上了權重,即每行為x1乘以對應的權重 xTx = xMat.T * (weights * xMat) #測試xTx是否可逆 if linalg.det(xTx) == 0.0: print("This matrix is singular, cannot do inverse") return #依然利用上節的公式求ws,不同在於每行的x和y都分別乘以對應的權重 ws = xTx.I * (xMat.T * (weights * yMat)) #返回局部加權預測值 return testPoint * ws def lwlrTest(testArr,xArr,yArr,k=1.0): #loops over all the data points and applies lwlr to each one m = shape(testArr)[0] yHat = zeros(m) for i in range(m): #循環利用lwlr函數計算出testArr每行向量對應的ws,算出加權預測值 yHat[i] = lwlr(testArr[i],xArr,yArr,k) return yHat
測試代碼如下:
測試截圖如下,當k=0.003時:
當k=0.01時:
當k=1時:
當k=1.0時權重很大,如同將所有的數據視為等權重,得出的最佳擬合直線與標准的回歸一致。使用k=0.01得到了非常好的效果,抓住了數據的潛在模式。使用k=0.003納人了太多的噪聲點,擬合的直線與數據點過於貼近,過擬合。
局部加權線性回歸也存在一個問題,即增加了計算量,因為它對每個點做預測時都必須使用整個數據集。k=0.01時可以得到很好的估計,但是同時看一下k=0.01的情況,就會發現大多數據點的權重都接近零。如果避免這些計算將可以減少程序運行時間,從而緩解因計算量增加帶來的問題。
8.3 示例:預測鮑魚的年齡
接下來,我們將回歸用於真實數據。在data目錄下有一份來自UCI數據集合的數據,記錄了鮑魚(一種介殼類水生動物)的年齡。鮑魚年齡可以從鮑魚殼的層數推算得到。
計算預測值和實際值方差的代碼如下:
def rssError(yArr,yHatArr): #yArr and yHatArr both need to be arrays return ((yArr-yHatArr)**2).sum()
測試截圖如下:
可以看到,使用較小的核將得到較低的誤差。那么,為什么不在所有數據集上都使用最小的核呢?這是因為使用最小的核將造成過擬合,對新數據不一定能達到最好的預測效果。下面看看它們在新數據上面的表現:
測試截圖如下:
從上述結果可以看到,在上面的三個參數中,核大小等於10時的測試誤差最小,但它在訓練集上的誤差卻是最大的。接下來再來和簡單的線性回歸做個比較:
簡單線性回歸達到了與局部加權線性回歸類似的效果。這也表明一點,必須在未知數據上比較效果才能選取到最佳模型。那么最佳的核大小是10嗎?或許是,但如果想得到更好的效果,應該用10個不同的樣本集做10次測試來比較結果。
本例展示了如何使用局部加權線性回歸來構建模型,可以得到比普通線性回歸更好的效果。局部加權線性回歸的問題在於,每次必須在整個數據集上運行。也就是說為了做出預測,必須保存所有的訓練數據。下面將介紹另一種提高預測精度的方法,並分析它的優勢所在。
8.4 縮減系數來”理解”數據
如果數據的特征比樣本點還多應該怎么辦?是否還可以使用線性回歸和之前的方法來做預測?答案是否定的,即不能再使用前面介紹的方法。這是因為在計算(XTX)−1的時候會出錯。
為了解決這個問題,統計學家引入了嶺回歸(ridge regression)的概念,這就是本節將介紹的第一種縮減方法。接着lasso法,該方法效果很好但計算復雜。本節最后介紹了第二種縮減方法,稱為前向逐步回歸,可以得到與lasso差不多的效果,且更容易實現。
8.4.1 嶺回歸
簡單說來,嶺回歸就是在矩陣XTX上加一個λI從而使得矩陣非奇異,進而能對XTX+λI求逆。其中矩陣I是一個m∗m的單位矩陣,對角線上元素全為1 ,其他元素全為0。而λ是一個用戶定義的數值,后面會做介紹。在這種情況下,回歸系數的計算公式將變成:
嶺回歸最先用來處理特征數多於樣本數的情況,現在也用於在估計中加人偏差,從而得到更好的估計。這里通過引入λ來限制了所有w之和,通過引人該懲罰項,能夠減少不重要的參數,這個技術在統計學中也叫做縮減(shrinkage )。
嶺回歸中的嶺是什么?
嶺回歸使用了單位矩陣乘以常量λ,我們觀察其中的單位矩陣, 可以看到值1貫穿整個對角線,其余元素全是0。形象地,在0構成的平面上有一條1組成的“嶺”,這就是嶺回歸中的“嶺”的由來
縮減方法可以去掉不重要的參數,因此能更好地理解數據。此外,與簡單的線性回歸相比,縮減法能取得更好的預測效果
嶺回歸,代碼如下所示:
#根據嶺回歸公式計算出ws def ridgeRegres(xMat,yMat,lam=0.2): xTx = xMat.T*xMat denom = xTx + eye(shape(xMat)[1])*lam #如果lam設定為0的時候一樣可能會產生錯誤,所以這里仍需要做一個檢查 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 #mean表示求出y的均值 yMean = mean(yMat,0) yMat = yMat - yMean #to eliminate X0 take mean off of Y #regularize X's #求出xArr各列均值 xMeans = mean(xMat,0) #calc mean then subtract it off #求出xArr各列方差 xVar = var(xMat,0) #calc variance of Xi then divide by it xMat = (xMat - xMeans)/xVar numTestPts = 30 #wMat表示嶺回歸系數矩陣,初始化為(30,n)維零數組 wMat = zeros((numTestPts,shape(xMat)[1])) #循環30次,根據不同的lambda填充ws for i in range(numTestPts): ws = ridgeRegres(xMat,yMat,exp(i-10)) wMat[i,:]=ws.T return wMat
處理完成后就可以在30個不同的λ下調用ridgeRegres()函數。注意,這里的λ應以指數級變化,這樣可以看出λ在取非常小的值時和取非常大的值時分別對結果造成的影響。最后將所有的回歸系數輸出到一個矩陣並返回。
測試截圖:
該圖繪出了回歸系數與log(λ)的關系。在最左邊,即λ最小時,可以得到所有系數的原始值(與線性回歸一致);而在右邊,系數全部縮減成0 ;在中間部分的某值將可以取得最好的預測效果。為了定量地找到最佳參數值,還需要進行交叉驗證。另外,要判斷哪些變量對結果預測最具有影響力,觀察它們對應的系數大小就可以。
8.4.2 lasso
不難證明,在增加如下約束時,普通的最小二乘法回歸會得到與嶺回歸的一樣的公式:
上式限定了所有回歸系數的平方和不能大於λ。使用普通的最小二乘法回歸在當兩個或更多的特征相關時,可能會得出一個很大的正系數和一個很大的負系數。正是因為上述限制條件的存在,使用嶺回歸可以避免這個問題。
與嶺回歸類似,另一個縮減方法lassso也對回歸系數做了限定,對應的約束條件如下:
唯一的不同點在於,這個約束條件使用絕對值取代了平方和。雖然約束形式只是稍作變化,結果卻大相徑庭:在λ足夠小的時候,一些系數會因此被迫縮減到0 ,這個特性可以幫助我們更好地理解數據。這兩個約束條件在公式上看起來相差無幾,但細微的變化卻極大地增加了計算復雜度(為了在這個新的約束條件下解出回歸系數,需要使用二次規划算法)。下面將介紹一個更為簡單的方法來得到結果,該方法叫做前向逐步回歸。
8.4.3 前向逐步回歸
前向逐步回歸算法可以得到與lasso差不多的效果,但更加簡單。它屬於一種貪心算法,即每一步都盡可能減少誤差。一開始,所有的權重都設為1,然后每一步所做的決策是對某個權重增加或減少一個很小的值。
該算法的偽代碼如下所示:
數據標准化,使其分布滿足0均值和單位方差
在每輪迭代過程中:
設置當前最小誤差lowestError為正無窮
對每個特征:
增大或縮小:
改變一個系數得到一個新的W
計算新W下的誤差
如果誤差Error小於當前最小誤差lowestError:設置Wbest等於當前的W
將W設置為新的Wbest
前向逐步線性回歸,代碼如下:
def regularize(xMat):#regularize by columns inMat = xMat.copy() inMeans = mean(inMat,0) #calc mean then subtract it off inVar = var(inMat,0) #calc variance of Xi then divide by it inMat = (inMat - inMeans)/inVar return inMat def stageWise(xArr,yArr,eps=0.01,numIt=100): xMat = mat(xArr); yMat=mat(yArr).T #mean()函數表示對矩陣求均值,axis=0指定按列求均值 yMean = mean(yMat,0) yMat = yMat - yMean #can also regularize ys but will get smaller coef #對xMat進行標准化處理,標准化處理函數為regularize() xMat = regularize(xMat) m,n=shape(xMat) #返回所有迭代中ws的變化情況,初始化為(numIt,n)維矩陣 returnMat = zeros((numIt,n)) #testing code remove #回歸系數ws初始化為(n,1)維零數組 ws = zeros((n,1)); wsTest = ws.copy(); wsMax = ws.copy() #迭代numIt次,每次迭代,循環n*2次(每個特征有增大和減小兩個方向),找出令rssError最小的方向(哪個特征,對應增大還是減小),保存ws,下次迭代在ws基礎上做更新 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
測試截圖如下:
上述結果中值得注意的是w1和w6都是0 ,這表明它們不對目標值造成任何影響,也就是說這些特征很可能是不需要的。另外,在參數eps(步長)設置為0.01的情況下,一段時間后系數就已經飽和並在特定值之間來回震盪,這是因為步長太大的緣故。這里會看到,第一個權重在0.04和0.05之間來回震盪
下面試着用更小的步長和更多的步數,測試結果截圖如下:
繪制returnMat,截圖如下:
接下來把這些結果與最小二乘法進行比較,后者的結果可以通過如下代碼獲得:
可以看到在5000次迭代以后,逐步線性回歸算法與常規的最小二乘法效果類似。
逐步線性回歸算法的實際好處並不在於能繪出這樣漂亮的圖,主要的優點在於它可以幫助人們理解現有的模型並做出改進。當構建了一個模型后,可以運行該算法找出重要的特征,這樣就有可能及時停止對那些不重要特征的收集。最后,如果用於測試,該算法每100次迭代后就可以構建出一個模型,可以使用類似於10折交叉驗證的方法比較這些模型,最終選擇使誤差最小的模型
當應用縮減方法(如逐步線性回歸或嶺回歸)時,模型也就增加了偏差(bias),與此同時卻減小了模型的方差
8.5 權衡偏差與方差
任何時候,一旦發現模型和測量值之間存在差異,就說出現了誤差。當考慮模型中的“噪聲”或者說誤差時,必須考慮其來源。你可能會對復雜的過程進行簡化,這將導致在模型和測量值之間出現“噪聲”或誤差,若無法理解數據的真實生成過程,也會導致差異的發生。另外,測量過程本身也可能產生“噪聲”或者問題。下面舉一個例子,8.1節和8.2節處理過一個從文件導人的二維數據。實話來講,這個數據是我自己造出來的,其具體的生成公式如下:
其中N(0,1)是一個均值為0、方差為1的正態分布。在8.1節中,我們嘗試過僅用一條直線來擬合上述數據。不難想到,直線所能得到的最佳擬合應該是3.0+1.7x這一部分。這樣的話,誤差部分就是0.1sin(30x)+0.06N(0,1)。在8.2節和8.3節,我們使用了局部加權線性回歸來試圖捕捉數據背后的結構。該結構擬合起來有一定的難度,因此我們測試了多組不同的局部權重來找到具有最小測試誤差的解。
圖8-8給出了訓練誤差和測試誤差的曲線圖,上面的曲線就是測試誤差,下面的曲線是訓練誤差。根據8.3節的實驗我們知道:如果降低核的大小,那么訓練誤差將變小。從圖8-8來看,從左到右就表示了核逐漸減小的過程。
一般認為,上述兩種誤差由三個部分組成:偏差、測量誤差和隨機噪聲。在8.2節和8.3節,我們通過引人了三個越來越小的核來不斷增大模型的方差。
8.4節介紹了縮減法,可以將一些系數縮減成很小的值或直接縮減為0,這是一個增大模型偏差的例子。通過把一些特征的回歸系數縮減到0,同時也就減少了模型的復雜度。例子中有8個特征,消除其中兩個后不僅使模型更易理解,同時還降低了預測誤差。圖8-8的左側是參數縮減過於嚴厲的結果,而右側是無縮減的效果。
方差是可以度量的。如果從鮑魚數據中取一個隨機樣本集(例如取其中100個數據)並用線性模型擬合,將會得到一組回歸系數。同理,再取出另一組隨機樣本集並擬合,將會得到另一組回歸系數。這些系數間的差異大小也就是模型方差大小的反映。上述偏差與方差折中的概念在機器學習十分流行並且反復出現。
下一節將介紹上述理論的應用:首先從拍賣站點抽取一些數據,再使用一些回歸法進行實驗來為數據找到最佳的嶺回歸模型。這樣就可以通過實際效果來看看偏差和方差間的折中效果
8.6示例:預測樂高玩具套裝的價格
用回歸法預測樂高套裝的價格
(1)收Google Shopping的API收集數據。
(2)准備數據:從返回的JSON數據中抽取價格。
(3)分析數據:可視化並觀察數據。
(4)訓練算法:構建不同的模型,釆用逐步線性回歸和直接的線性回歸模型。
(5)測試算法:使用交叉驗證來測試不同的模型,分析哪個效果最好。
(6)使用算法:這次練習的目標就是生成數據模型。
在這個例子中,我們將從不同的數據集上獲取價格,然后對這些數據建立回歸模型。需要做的第一件事就是如何獲取數據。
8.6.1 收集數據:使用Google購物的API
Google已經為我們提供了一套購物的API來抓取價格。在使用處1之前,需要注冊一個Goolge的賬號,然后訪問Google API的控制台來確保購物API可用。完成之后就可以發送HTTP請求,API將以JSON格式返回所需的產品信息。Python提供了JSON解析模塊,我們可以從返回的JSON格式里整理出所需數據。詳細的API介紹可以參見:http://code.google.com/apis/shopping/search/v1/getting_started.html。
購物信息的獲取函數,代碼如下:
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) #通過google api獲取對應商品編號的數據 pg = urllib2.urlopen(searchURL) #將數據轉化為json格式 retDict = json.loads(pg.read()) #遍歷json數據,填充retX,retY 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)
測試截圖如下:(寫的時候沒有翻牆。。。)
8.6.2 訓練算法:建立模型
使用線性回歸得到的模型如下:
$55319.97−27.59Year−0.00268∗NumPieces−11.22∗NewOrUsed+2.57∗originalprice
這個模型的預測效果非常好,但模型本身並不能令人滿意。它對於數據擬合得很好,但看上去沒有什么道理。從公式看,套裝里零部件越多售價反而會越低。另外,該公式對新套裝也有一定的懲罰。
下面使用縮減法中一種,即嶺回歸再進行一次實驗。前面討論過如何對系數進行縮減,但這
次將會看到如何用縮減法確定最佳回歸系數。
交叉驗證測試嶺回歸,代碼如下:
def crossValidation(xArr,yArr,numVal=10): m = len(yArr) indexList = range(m) #errorMat保存不同訓練樣本(默認為10次不同的訓練樣本和測試樣本)對應30個lambda(ridgeTest()將lambda按指數級遞減得到30個不同lambda對應的相關系數)的rssError,初始化為(10,30)維零矩陣 errorMat = zeros((numVal,30))#create error mat 30columns numVal rows for i in range(numVal): trainX=[]; trainY=[] testX = []; testY = [] #shuffle()表示對range(m)重新洗牌,即讓每次迭代對應的訓練集和測試集都不一樣 random.shuffle(indexList) for j in range(m):#create training set based on first 90% of values in indexList 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) #get 30 weight vectors from ridge for k in range(30):#loop over all of the ridge estimates matTestX = mat(testX); matTrainX=mat(trainX) meanTrain = mean(matTrainX,0) varTrain = var(matTrainX,0) matTestX = (matTestX-meanTrain)/varTrain #regularize test with training params yEst = matTestX * mat(wMat[k,:]).T + mean(trainY)#test ridge results and store errorMat[i,k]=rssError(yEst.T.A,array(testY)) #print errorMat[i,k] meanErrors = mean(errorMat,0)#calc avg performance of the different ridge weight vectors #求出最小的meanErrors minMean = float(min(meanErrors)) #meanErros==minMean的那一列對應的即是使誤差最小的lambda,而這一列對應的即為找出最佳相關系數的wMat的行數 bestWeights = wMat[nonzero(meanErrors==minMean)] #can unregularize to get model #when we regularized we wrote Xreg = (x-meanX)/var(x) #we can now write in terms of x not Xreg: x*w/var(x) - meanX/var(x) +meanY xMat = mat(xArr); yMat=mat(yArr).T meanX = mean(xMat,0); varX = var(xMat,0) #注意,嶺回歸將xMat和yMat標准化了,而線性回歸不用標准化,所以我們要將數據"還原" unReg = bestWeights/varX print("the best model from Ridge Regression is:\n",unReg) print("with constant term: ",-1*sum(multiply(meanX,unReg)) + mean(yMat))
來看一下整體的運行效果,在%獅如叫丫中輸人程序清單8-6中的代碼並保存,然后執行如下命令:
可以看到,該結果與最小二乘法沒有太大差異。我們本期望找到一個更易於理解的模型,顯然沒有達到預期效果。為了達到這一點,我們來看一下在縮減過程中回歸系數是如何變化的,輸人下面的命令
這些系數是經過不同程度的縮減得到的。首先看第1行,第4項比第2項的系數大5倍,比第1項大57倍。這樣看來,如果只能選擇一個特征來做預測的話,我們應該選擇第4個特征,也就是原始價格。如果可以選擇2個特征的話,應該選擇第4個和第2個特征。
這種分析方法使得我們可以挖掘大量數據的內在規律。在僅有4個特征時,該方法的效果也許並不明顯;但如果有100個以上的特征,該方法就會變得十分有效:它可以指出哪些特征是關鍵的,而哪些特征是不重的。
本章小結
與分類一樣,回歸也是預測目標值的過程。回歸與分類的不同點在於,前者預測連續型變量,而后者預測離散型變量。回歸是統計學中最有力的工具之一。在回歸方程里,求得特征對應的最佳回歸系數的方法是最小化誤差的平方和。給定輸人矩陣X,如果X的逆存在並可以求得的話,回歸法都可以直接使用。數據集上計算出的回歸方程並不一定意味着它是最佳的,可以便用預測值yHat和原始值y的相關性來度量回歸方程的好壞。
當數據的樣本數比特征數還少時候,矩陣XTX的逆不能直接計算。即便當樣本數比特征數多時,XTX的逆仍有可能無法直接計算,這是因為特征有可能高度相關。這時可以考慮使用嶺回歸,因為當XTX的逆不能計算時,它仍保證能求得回歸參數。
嶺回歸是縮減法的一種,相當於對回歸系數的大小施加了限制。另一種很好的縮減法是lasso。Lasso難以求解,但可以使用計算簡便的逐步線性回歸方法來求得近似結果。
縮減法還可以看做是對一個模型增加偏差的同時減少方差。偏差方差折中是一個重要的概念,可以幫助我們理解現有模型並做出改進,從而得到更好的模型。
本章介紹的方法很有用。但有些時候數據間的關系可能會更加復雜,如預測值與特征之間是非線性關關系,這種情況下使用線性的模型就難以擬合。