機(jī)器學(xué)士實(shí)戰(zhàn)(筆記):第 8 章 預(yù)測(cè)數(shù)值型數(shù)據(jù):回歸

第二部分 利用回歸預(yù)測(cè)數(shù)值型數(shù)據(jù)

第 8 章 預(yù)測(cè)數(shù)值型數(shù)據(jù):回歸

[TOC]

本章內(nèi)容

  • 線性回歸
  • 局部加權(quán)線性回歸
  • 嶺回歸和逐步線性回歸
  • 預(yù)測(cè)鮑魚年齡和玩具售價(jià)

1. 用線性回歸找到最佳擬合直線

線性回歸:

  • 優(yōu)點(diǎn):結(jié)果易于理解,計(jì)算熵不復(fù)雜。
  • 缺點(diǎn):對(duì)非線性的數(shù)據(jù)擬合不好。
  • 適用數(shù)類型:數(shù)值型和標(biāo)稱型數(shù)據(jù)。

回歸的目的是預(yù)測(cè)數(shù)值型的目標(biāo)值。最直接的辦法是依據(jù)輸入寫成一個(gè)目標(biāo)值的計(jì)算公式。

y=a_1*x_1+a_2*x_2

這就是回歸方程,其中的 a1 和 a2 稱作回歸系數(shù)(regression weights),求這些回歸系數(shù)的過程就是回歸。一旦有了這些回歸系數(shù),再給定輸入,做預(yù)測(cè)就非常容易了,具體的做法是用回歸系數(shù)乘以輸入值,再將結(jié)果全部加在一起,就得到了預(yù)測(cè)值。(這里的回歸都是指線性回歸(linear regression)。

回歸的一般方法:

  1. 收集數(shù)據(jù):采用任意方法收集數(shù)據(jù)。
  2. 準(zhǔn)備數(shù)據(jù):回歸需要數(shù)值型數(shù)據(jù),標(biāo)稱型數(shù)據(jù)將被轉(zhuǎn)成二值型數(shù)據(jù)。
  3. 分析數(shù)據(jù):繪制出數(shù)據(jù)的可視化二維圖將有助于對(duì)數(shù)據(jù)做出理解和分析,在采用縮減法求得新回歸系數(shù)之后,可以將新擬合線繪制在圖上作為對(duì)比。
  4. 訓(xùn)練算法:找到回歸系數(shù)。
  5. 測(cè)試算法:使用 R2 或者預(yù)測(cè)值和數(shù)據(jù)的擬合度,來分析模型的效果。
  6. 使用算法:使用回歸,可以在給定輸入的時(shí)候預(yù)測(cè)出一個(gè)數(shù)值,這是對(duì)分類方法的提升,因?yàn)檫@樣可以預(yù)測(cè)連續(xù)型數(shù)值而不僅僅是離散的類別標(biāo)簽。

應(yīng)當(dāng)怎樣從一大堆數(shù)據(jù)里求出回歸方程呢?假定輸入數(shù)據(jù)存放在矩陣 X 中,而回歸系數(shù)存放在向量 W 中,那么對(duì)于給定的數(shù)據(jù) x1,預(yù)測(cè)結(jié)果將會(huì)通過

Y_1=X^T_1 W

給出?,F(xiàn)在的問題是,有一些 x 個(gè)對(duì)應(yīng)的 Y,怎樣才能找到 w 呢?一個(gè)常用的方法就是找出誤差最小的 w。這里的誤差是指預(yù)測(cè) y 值和真實(shí) y 值之間的差值,使用該差值的簡(jiǎn)單累加將使得正差值和發(fā)差值相互抵消,所以使用平方誤差公式:

\sum^m_{i=1}(y_i-x^T_i w)^2

用矩陣表示為:

(y-Xw)^T(y-Xw)

如果對(duì) w 求導(dǎo),得到

X^T(y-Xw)

令其等于零,解出 w 如下:

\hat{w}=(X^T X)^{-1}X^T y

w 上方的小標(biāo)記表示,這是當(dāng)前可以估計(jì)出的 w 的最優(yōu)解。

值得注意的是,上述公式中包含

X^T X^{-1}

,也就是需要對(duì)矩陣求逆,因此這個(gè)方程只在逆矩陣存在的時(shí)候適用(代碼中可以用偽逆矩陣)。上述的最佳 w 求解的統(tǒng)計(jì)學(xué)中的常見問題,除了矩陣方法外還有其他方法可以解決。該方法也稱為 OLS,即普通最小二乘法(ordinary least squares)。

對(duì)于圖8-1的散點(diǎn)圖,下面來介紹如何給出該數(shù)據(jù)的最佳擬合直線。

圖8-1

添加 loadDataSet() 函數(shù):

def loadDataSet(fileName):
    """
    讀取文本文件
    :param fileName:
    :return:
    """
    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

添加 standRegres 函數(shù):

def standRegres(xArr, yArr):
    """計(jì)算最佳擬合直線"""
    xMat = np.mat(xArr)
    yMat = np.transpose(np.mat(yArr))
    xTx = np.transpose(xMat) * xMat
    if np.linalg.det(xTx) == 0.0: # 計(jì)算 xTx 的行列式,為零則不能計(jì)算逆矩陣
        print("this matrix is singular,cannot do inverse")
        return
    ws = xTx.I * (xMat.T * yMat)
    # ws = np.linalg.solve(xTx, xMat.T * yMat) # 偽逆矩陣,可以不判斷行列式是否為零
    return ws

測(cè)試:

xArr,yArr = loadDataSet('ex0.txt')
ws = standRegres(xArr,yArr) # 回歸系數(shù)
# print(ws) # [[ 3.00774324] [ 1.69532264]]
xMat = np.mat(xArr)
yMat = np.mat(yArr)
yHat = xMat * ws # 預(yù)測(cè)的 y 值: y=ws[0]+ws[1]*X1
print(np.cov(yHat.T, yMat)) # 協(xié)方差 [[ 0.24664409  0.24664409] [ 0.24664409  0.25345439]]
print(np.corrcoef(yHat.T, yMat)) # 相關(guān)系數(shù) [[ 1.  0.98647356] [ 0.98647356  1.  ]]

# 繪制數(shù)據(jù)集散點(diǎn)圖和最佳擬合直線圖
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()
圖8-2

幾乎任一數(shù)據(jù)集都可以用上述方法建立模型,那么,如何判斷這些模型的好壞呢?比較一下圖8-3 的兩個(gè)子圖,如果在兩個(gè)數(shù)據(jù)集上分別作線性回歸,將得到完全一樣的模型(擬合直線)。顯然兩個(gè)數(shù)據(jù)是不一樣的,那么模型分別在二者上的效果如何?我們當(dāng)如何比較這些效果的好壞?有兩種方法可以計(jì)算預(yù)測(cè)值 yHat 序列和真實(shí)值 y 序列的匹配程度,那就是計(jì)算這兩個(gè)序列的相關(guān)系數(shù)。

圖8-3

在 numpy 中提供了 corrcoef 來計(jì)算預(yù)測(cè)值與真實(shí)值的相關(guān)性.

協(xié)方差:

Cov(X,Y)=\frac{1}{m}\sum^m_{i=1}(x_i-\bar{x})(y_i-\bar{y})

相關(guān)系數(shù):

r=\frac{Cov(X,Y)}{\sqrt{\delta_x}\sqrt{\delta_y}}

其中,

\sqrt{\delta_x}$$、$$\sqrt{\delta_y}

表示 X、Y 的方差,并且 |r|<=1。

該矩陣包含所有兩兩組合的相關(guān)系數(shù)??梢钥吹?,對(duì)角線上的數(shù)據(jù)是 1.0,因?yàn)?yMat 和自己的匹配是最完美的,而 yHat 和 yMat 的相關(guān)系數(shù)為 0.98.

2. 局部加權(quán)線性回歸

線性回歸的一個(gè)問題是有可能出現(xiàn)欠擬合現(xiàn)象,因?yàn)樗蟮氖蔷哂凶钚【秸`差的無偏估計(jì)。顯而易見,如果模型欠擬合將不能取得最好的預(yù)測(cè)效果,所以有些方法允許在估計(jì)中引入一些偏差,從而降低預(yù)測(cè)的均方誤差。

其中一個(gè)方法是局部加權(quán)線性回歸(Locally Weighted Linear Regression,LWLR)。在該算法中,我們給待預(yù)測(cè)點(diǎn)附近的每個(gè)點(diǎn)賦予一定的權(quán)重;然后與8.1節(jié)類似,在這個(gè)子集上基于最小均方差來進(jìn)行普通的回歸。與 kNN 一樣,這種算法每次均需要事先取出對(duì)應(yīng)的數(shù)據(jù)子集。該算法解出回歸系數(shù) w 的形式如下:

\hat{w}=(X^T WX)^{-1}X^TWy

其中 w 是一個(gè)矩陣,用來給每個(gè)數(shù)據(jù)點(diǎn)賦予權(quán)重。

LWLR 使用“核”來對(duì)附近的點(diǎn)賦予更高的權(quán)重。核的類型可以自由選擇,最常用的核就是高斯核,其對(duì)應(yīng)的權(quán)重如下:

w(i,i)=exp(\frac{|x^{(i)}-x|}{-2k^2})

這樣就構(gòu)建了一個(gè)只含對(duì)角元素的權(quán)重矩陣 w,并且點(diǎn) x 與 x(i) 越近,w(i,i) 將會(huì)越大。上述公式包含一個(gè)需要用戶指定的參數(shù) k,它決定了對(duì)附近的點(diǎn)賦予多大的權(quán)重,這也就是使用 LWLR 時(shí)唯一需要考慮的參數(shù),在圖8-4中可以看到參數(shù) k 與權(quán)重的關(guān)系:

圖8-4

添加 lwlr() 函數(shù):

def lwlr(testPoint, xArr, yArr, k=1.0):
    """
    局部加權(quán)線性回函數(shù)
    :param testPoint:
    :param xArr:
    :param yArr:
    :param k:用于控制衰減速度
    :return:
    """
    xMat = np.mat(xArr)
    yMat = np.mat(yArr).T
    m = np.shape(xMat)[0]
    weights = np.mat(np.eye((m))) # 創(chuàng)建對(duì)角矩陣,階數(shù)等于樣本點(diǎn)個(gè)數(shù)
    for j in range(m):
        # 隨著樣本點(diǎn)與待預(yù)測(cè)點(diǎn)距離的遞增,權(quán)重值大小以指數(shù)級(jí)衰減
        diffMat = testPoint - xMat[j,:]
        weights[j,j] = np.exp(diffMat*diffMat.T/(-2.0*k**2)) # 參數(shù) k,控制衰減的速度
    xTx = xMat.T * (weights * xMat)
    if np.linalg.det(xTx) == 0.0:
        print('this matrix is singular, cannot do inverse')
    ws = xTx.I * (xMat.T * (weights * yMat))
    return testPoint * ws

添加 lwlrTest() 函數(shù):

def lwlrTest(testArr, xArr, yArr, k=1.0):
    """
    用于為數(shù)據(jù)集中每個(gè)點(diǎn)調(diào)用 lwlr(),這有助于求解 k 的大小
    :param testArr:
    :param xArr:
    :param yArr:
    :param k:
    :return:
    """
    m = np.shape(testArr)[0]
    yHat = np.zeros(m)
    for i in range(m):
        yHat[i] = lwlr(testArr[i], xArr, yArr, k)
    return yHat

測(cè)試:

xArr, yArr = loadDataSet('ex0.txt')
yHat = lwlrTest(xArr, xArr, yArr, 0.01) # k=1, 0.01, 0.003
xMat = np.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], np.mat(yArr).T.flatten().A[0], s=2, c='red')
plt.show()

可以觀察到如圖8-5所示的效果。圖8-5 給出了 k 在不同取值下的結(jié)果圖。

圖8-5

局部加權(quán)線性回歸也存在一個(gè)問題,即增加了計(jì)算量,因?yàn)樗鼘?duì)每個(gè)點(diǎn)做預(yù)測(cè)時(shí)都必須使用整個(gè)數(shù)據(jù)集。從圖8-5可以看出,k=0.01 時(shí)可以得到很好的估計(jì),但是同時(shí)看到涂8-4中 k=0.01 的情況,就會(huì)發(fā)現(xiàn)大多數(shù)據(jù)點(diǎn)的權(quán)重都接近零。如果避免這些計(jì)算將可以減少程序運(yùn)行時(shí)間,從而緩解因計(jì)算量增加帶來的問題。

3. 示例:預(yù)測(cè)鮑魚的年齡

在data目錄下有一份來自 UCI 數(shù)據(jù)集合的數(shù)據(jù),記錄了鮑魚的年齡。鮑魚年齡可以從鮑魚殼的層數(shù)推算得到。

預(yù)測(cè)代碼:

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(rssError(abY[0:99], yHat01.T)) # 56.7886874305
print(rssError(abY[0:99], yHat1.T)) # 429.89056187
print(rssError(abY[0:99], yHat10.T)) # 549.118170883

可以看到,使用較小的核將得到較低的誤差,那么,為什么不在所有數(shù)據(jù)集上都使用最小的核呢?這是因?yàn)?使用最小的核將造成過擬合,對(duì)新數(shù)據(jù)不一定能達(dá)到最好的預(yù)測(cè)效果。下面就來看看它們?cè)谛聰?shù)據(jù)上的表現(xiàn):

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(rssError(abY[100:199], yHat01.T)) # 57913.5155016
print(rssError(abY[100:199], yHat1.T)) # 573.52614419
print(rssError(abY[100:199], yHat10.T)) # 517.571190538

從上述結(jié)果可以看到,核大小等于 10 時(shí)的測(cè)試誤差最小,但它在訓(xùn)練集上的誤差卻最大的。接下來和簡(jiǎn)單的線性回歸做個(gè)比較:

ws = standRegres(abX[0:99], abY[0:99])
yHat = np.mat(abX[100:199]) * ws
print(rssError(abY[100:199], yHat.T.A)) # 518.636315325

簡(jiǎn)單線性回歸達(dá)到了與局部加權(quán)線性回歸類似的結(jié)果,這也表明一點(diǎn),必須在未知數(shù)據(jù)上比較效果才能選取到最佳模型。

4. 縮減系數(shù)來“理解”數(shù)據(jù)

如果數(shù)據(jù)的特征比樣本點(diǎn)還多應(yīng)該怎么辦?是否還可以使用線性回歸和之前的額方法來做預(yù)測(cè)?答案是否定的,即不能再使用前面介紹的方法。這是因?yàn)樵谟?jì)算

(X^TX)^{-1}

的時(shí)候會(huì)出錯(cuò)。

如果特征比樣本點(diǎn)還多 (n>m) 也就是說輸入數(shù)據(jù)的矩陣 X 不是滿秩矩陣。非滿秩矩陣在求逆時(shí)會(huì)出問題。

這節(jié)介紹的三種方法就是來解決這個(gè)問題。

4.1 嶺回歸

嶺回歸(ridge regression)就是在矩陣 $$X^TX$$ 上加一個(gè) $$\lambda I$$ 從而使得矩陣非奇異,進(jìn)而能對(duì) $$X^T X+\lambda I$$ 求逆。其中矩陣 I 是一個(gè) m*m 的單位矩陣。而 lambda 是一個(gè)用戶定義數(shù)值,后面會(huì)做介紹。在這種情況下, 回歸系數(shù)的計(jì)算公式將變成:

\hat{w}=(X^T X+\lambda I)^{-1}X^T y

嶺回歸最先用來處理特征多余樣本數(shù)的情況,現(xiàn)在也用于在估計(jì)中加入偏差,從而得到更好的估計(jì)。這里通過引入 lambda 來限制了所有 w 之和,通過引入該懲罰項(xiàng),能夠減少不重要的參數(shù),這個(gè)技術(shù)在統(tǒng)計(jì)學(xué)中也叫做縮減(shrinkage)。

添加 ridgeRegres() 函數(shù):

def ridgeRegres(xMat, yMat, lam=0.2):
    """
    計(jì)算回歸系數(shù)
    :param xMat: 
    :param yMat: 
    :param lam: 
    :return: 
    """
    xTx = xMat.T * xMat
    denom = xTx + np.eye(np.shape(xMat)[1]) * lam # 
    if np.linalg.det(denom) == 0.0:
        print("this matrix is singular,cannot do inverse")
        return
    ws = denom.I * (xMat.T * yMat)
    # ws = np.linalg.solve(xTx + np.eye(np.shape(xMat)[1]) * lam, xMat.T * yMat) # 用偽逆矩陣求解
    return ws

添加 ridgeTest() 函數(shù):

def ridgeTest(xArr, yArr):
    """
    在一組lam上測(cè)試結(jié)果
    :param xArr:
    :param yArr:
    :return:
    """
    xMat = np.mat(xArr)
    yMat = np.mat(yArr).T
    yMean = np.mean(yMat, 0) # 求均值
    yMat = yMat - yMean
    xMeans = np.mean(xMat, 0) # 求均值
    xVar = np.var() # 求方差
    xMat = (xMat - xMeans)/xVar # 數(shù)據(jù)標(biāo)準(zhǔn)化:所有特征都減去各自的均值并除以方差
    numTestPts = 30
    wMat = np.zeros((numTestPts, np.shape(xMat)[1]))
    for i in range(numTestPts):
        ws = ridgeRegres(xMat, yMat, np.exp(i-10))
        wMat[i,:] = ws.T
    return wMat

下面看一下鮑魚數(shù)據(jù)集上的運(yùn)行結(jié)果:

abX, abY = loadDataSet('abalone.txt')
ridgeWeights = ridgeTest(abX, abY)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(ridgeWeights)
plt.show()

這樣就得到了 30 個(gè)不同 lambda 所對(duì)應(yīng)回歸系數(shù)。圖8-6是所見的額效果圖:

圖8-6

該圖繪制出了回歸系數(shù)與 log(lam) 的關(guān)系。在最左邊,即 lambda 最小時(shí),可以得到所有系數(shù)的原始值(與線性回歸一致);而在右邊,系數(shù)全部縮減為 0;在中間部分的某值將可以取得最好的預(yù)測(cè)效果。為了定量地找到最佳參數(shù)值,還需要進(jìn)行交叉驗(yàn)證。另外,要判斷哪些變量對(duì)結(jié)果預(yù)測(cè)最具有影響力,在圖8-6 中觀察它們對(duì)應(yīng)的系數(shù)大小就可以。

4.2 lasso

不難證明,在增加如下約束時(shí),普通的最小二乘法回歸會(huì)得到與嶺回歸的一樣的公式:

\sum^n_{k=1}w^2_k \leq \lambda

上式限定了所有回歸系數(shù)的平方和不能大于 lambda。使用普通的最小二乘法回歸在當(dāng)兩個(gè)或更多的特征相關(guān)時(shí),可能會(huì)得出一個(gè)很大的正系數(shù)和一個(gè)很大的負(fù)系數(shù)。正式因?yàn)樯鲜鱿拗茥l件的存在,使用嶺回歸可以避免這個(gè)問題。

與嶺回歸類似,另一個(gè)縮減方法 lasso 也對(duì)回歸系數(shù)做了限定,對(duì)應(yīng)的約束條件如下:

\sum^n_{k=1}|w_k| \leq \lambda

唯一的不同點(diǎn)在于,這個(gè)約束條件使用絕對(duì)值取代了平方和。在 lambda 足夠小時(shí),一些系數(shù)會(huì)因此被迫縮減到 0,這個(gè)特性可以幫助我們更好的理解數(shù)據(jù),這兩個(gè)約束條件在公式上看起來相差無幾,但細(xì)微的變化卻極大的增加了計(jì)算復(fù)雜度(為了在這個(gè)新的約束條件下解出回歸系數(shù),需要使用二次規(guī)劃算法)。下面將介紹一個(gè)更為簡(jiǎn)單的方法來得到結(jié)果,該方法叫做前向逐步回歸。

4.3 前向逐步回歸

前向逐步回歸算法可以得到與 lasso 差不多的效果,但更加簡(jiǎn)單。它屬于一種貪心算法,即每一步都盡可能減少誤差。一開始,所有的權(quán)重都設(shè)為 1,然后每一步所做的決策時(shí)對(duì)某個(gè)權(quán)重增加或減少一個(gè)很小的值。

該算法的偽代碼如下:

數(shù)據(jù)標(biāo)準(zhǔn)化,使其分布滿足 0 均值和單位方差
在每輪迭代過程中:
    設(shè)置當(dāng)前最小誤差啊 lowestError 為正無窮
    對(duì)每個(gè)特征:
        增大或縮?。?            改變一個(gè)系數(shù)得到一個(gè)新的 W
            計(jì)算新 W 下的誤差
            如果誤差 Error 小于當(dāng)前最小誤差 lowestError:設(shè)置 Wbest 等于當(dāng)前的 W
        將 W 設(shè)置為新的 WBest

添加 regularize() 函數(shù):

def regularize(xMat):
    """
    把特征按照均值為0,方差為1進(jìn)行標(biāo)準(zhǔn)化
    :param xMat:
    :return:
    """
    inMat = xMat.copy()
    inMeans = np.mean(inMat, 0)
    inVar = np.var(inMat, 0)
    inMat = (inMat - inMeans)/inVar
    return inMat

添加 stageWise() 函數(shù):

def stageWise(xArr, yArr, eps=0.01, numIt=100):
    """
    逐步線性回歸算法
    :param xArr:
    :param yArr:
    :param eps: 步長(zhǎng)
    :param numIt: 迭代次數(shù)
    :return:
    """
    xMat =np.mat(xArr)
    yMat = np.mat(yArr).T
    yMean = np.mean(yMat, 0)
    yMat = yMat - yMean
    xMat = regularize(xMat)
    m,n = np.shape(xMat)
    returnMat = np.zeros((numIt,n))
    ws = np.zeros((n,1))
    wsTest = ws.copy() # 為了實(shí)現(xiàn)貪心算法建立了ws的兩個(gè)副本
    wsMax = ws.copy()
    for i in range(numIt): # 迭代
        print('ws.T:',ws.T)
        lowestError = float('inf')
        for j in range(n):
            for sign in [-1,1]: # 兩次循環(huán),分別計(jì)算增加或減少該特征對(duì)誤差的影響
                wsTest = ws.copy()
                wsTest[j] += eps*sign
                yTest = xMat*wsTest
                rssE = rssError(yMat.A, yTest.A) # 得到平方誤差
                if rssE < lowestError: # 經(jīng)過與所有的誤差比較后取最小的誤差
                    lowestError = rssE
                    wsMax = wsTest
        ws = wsMax.copy()
        returnMat[i,:] = ws.T
    return returnMat

測(cè)試一下實(shí)際效果:

xArr, yArr = loadDataSet('abalone.txt')
# print(stageWise(xArr,yArr,0.01,200))
print(stageWise(xArr,yArr,0.001,5000))

與最小二乘法進(jìn)行比較,發(fā)現(xiàn)在5000次迭代后,逐步線性回歸算法與常規(guī)的最小二乘法效果類似。使用 0.005 的 epsilon 值并經(jīng)過 1000 次迭代后的結(jié)果參見圖8-7:

圖8-7

逐步線性回歸算法的實(shí)際好處并不在于能繪出圖8-7這樣漂亮的圖,主要的優(yōu)點(diǎn)在于它可以幫助人們理解現(xiàn)有的模型并作出改進(jìn)。當(dāng)構(gòu)建一個(gè)模型后,可以運(yùn)行該算法找出重要的特征,這樣就有可能及時(shí)停止對(duì)那些不重要特征的收集。最后,如果用于測(cè)試,該算法每 100 次迭代后就可以構(gòu)建出一個(gè)模型,可以使用類似于 10 折交叉驗(yàn)證比較這些模型,最終選擇使誤差最小的模型。

當(dāng)應(yīng)用縮減方法(如逐步線性回歸或嶺回歸)時(shí),模型也就增加了**偏差(bias),與此同時(shí)卻減小了模型的方差。

5. 權(quán)衡偏差與方差

圖8-8給出了訓(xùn)練誤差和測(cè)試誤差的曲線圖,上面的曲線圖就是測(cè)試誤差,下面的曲線是訓(xùn)練誤差。根據(jù)8.3節(jié)的實(shí)驗(yàn)我們知道:如果降低核的大小,那么訓(xùn)練誤差將變小。從圖8-8來看,從左到右就表示了核逐漸減小的過程。

圖8-8

一般認(rèn)為,上述兩種誤差由三個(gè)部分組成:偏差、測(cè)量誤差和隨機(jī)噪聲。在8.2節(jié)和8.3節(jié),我們通過引入了三個(gè)越來越小的核來不斷增大模型方差。

8.4節(jié)介紹了縮減法,可以將一些系數(shù)縮減成很小的值或直接縮減為0,這是一個(gè)增大模型偏差的例子。通過把一些特征的回歸系數(shù)縮減到 0 ,同時(shí)也就減少了模型的復(fù)雜度。例子中有8個(gè)特征,消除了其中兩個(gè)后不僅使模型更易理解,同時(shí)還降低了預(yù)測(cè)誤差。圖8-8左側(cè)是參數(shù)縮減過于嚴(yán)厲的結(jié)果,右側(cè)是無縮減的效果。

6. 示例:預(yù)測(cè)樂高玩具套裝的價(jià)格

一種樂高套裝基本上在幾年后就會(huì)停產(chǎn),但樂高的收藏著之間仍會(huì)在停產(chǎn)后彼此交易,為了給樂高套裝估價(jià),下面將用本章的回歸技術(shù)來建立一個(gè)預(yù)測(cè)模型。

示例:用回歸法預(yù)測(cè)樂高套裝的價(jià)格:

  1. 收集數(shù)據(jù):用 google shopping 的 api 收集數(shù)據(jù)。
  2. 準(zhǔn)備數(shù)據(jù):從返回的 json 數(shù)據(jù)中抽取價(jià)格。
  3. 分析數(shù)據(jù):可視化并觀察數(shù)據(jù)。
  4. 訓(xùn)練算法:構(gòu)建不同的模型,采用逐步線性回歸和直接的線性回歸模型。
  5. 測(cè)試算法:使用交叉驗(yàn)證來測(cè)試不同的模型,分析哪個(gè)效果更好。
  6. 使用算法:這次練習(xí)的目的就是生成數(shù)據(jù)模型。

6.1 收集數(shù)據(jù):使用 google 購物的 api

詳細(xì) api 介紹可參見:http://code.google.com/apis/shopping/search/v1/gettting_started.html

添加 searchForSet() 函數(shù):

from time import sleep
import json
import urllib

def searchForSet(retX, retY, setNum, yr, numPce, origPrc):
    """
    調(diào)用google 購物 api 并保證數(shù)據(jù)抽取的正確性
    :param retX:
    :param retY:
    :param setNum:
    :param yr:
    :param numPce:
    :param origPrc:
    :return:
    """
    sleep(10) # 防止短時(shí)間內(nèi)有過多的 api 調(diào)用
    myAPIstr = 'get from code.google.com'
    searchURL = 'http://www.googleapis.com/shopping/search/v1/public/products?key=%s&country=US&q=lego+%d&alt=json' % (myAPIstr,setNum)
    pg = urllib.request.urlopen(searchURL)
    retDict = json.loads(pg.read())
    for i in range(len(retDict['items'])):
        try:
            currItem = retDict['items'][i]
            if currItem['product']['condition'] == 'new': # 判斷是否新產(chǎn)品
                newFlag = 1
            else: newFlag = 0
            listOfInv = currItem['product']['inventories']
            for item in listOfInv:
                sellingPrice = item['price']
                if sellingPrice > origPrc * 0.5: # 如果一個(gè)套裝的價(jià)格比原始價(jià)格低一半以上就認(rèn)為是不完整,過濾掉
                    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)

測(cè)試(無法訪問google api):

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)

lgX = []
lgY = []
setDataCollect(lgX, lgY)

6.2 訓(xùn)練算法:建立模型

上一節(jié)從網(wǎng)上收集到了一些真實(shí)的數(shù)據(jù),下面將為這些數(shù)據(jù)構(gòu)建一個(gè)模型。構(gòu)建的模型可以對(duì)售價(jià)作出預(yù)測(cè),并幫助我們理解現(xiàn)有數(shù)據(jù)。

使用常規(guī)的線性回歸:

# print(np.shape(lgX)) # (58, 4)
lgX1 = np.mat(np.ones((58,5))) # 創(chuàng)建一個(gè)全 1 的矩陣
lgX1[:,1:5] = np.mat(lgX) # 將原數(shù)據(jù)矩陣 lgX 復(fù)制到新數(shù)據(jù)矩陣 lgX1 的第 1 到第 5 列
ws = standRegres(lgX1, lgY) # 回歸算法
print(lgX1[0]*ws)
print(lgX1[-1]*ws)
print(lgX1[43]*ws)

使用嶺回歸確定最佳回歸系數(shù):

def crossValidation(xArr, yArr, numVal=10):
    """
    交叉驗(yàn)證測(cè)試嶺回歸
    :param xArr:
    :param yArr:
    :param numVal: 交叉驗(yàn)證的次數(shù)
    :return:
    """
    m = len(yArr)
    indexList = range(m)
    errorMat = np.zeros((numVal, 30))
    for i in range(numVal): # 數(shù)據(jù)分為訓(xùn)練集和測(cè)試集
        trainX = []
        trainY = []
        testX = []
        testY = []
        np.random.shuffle(indexList) # 對(duì)數(shù)據(jù)混洗
        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) # wMat 保存嶺回歸中的所有回歸系數(shù)
    for k in range(30):
        # 用訓(xùn)練時(shí)的參數(shù)將測(cè)試數(shù)據(jù)標(biāo)準(zhǔn)化
        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)]
    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) # 標(biāo)準(zhǔn)化后的數(shù)據(jù)還原
    print("with constant term:",-1 * sum(np.multiply(meanX, unReg)) + np.mean(yMat))

運(yùn)行:

crossValidation(lgX, lgY, 10)

我們本期望找到一個(gè)更易于理解的模型,顯然沒有達(dá)到預(yù)期結(jié)果。為了達(dá)到這一點(diǎn),我們來看一下在縮減過程中回歸系數(shù)是如何變化的,輸入一下的命令:

print(ridgeTest(lgX, lgY)) 

這些系數(shù)是經(jīng)過不同程度的縮減得到的。

這種分析方法似的我們可以挖掘大量數(shù)據(jù)的內(nèi)在規(guī)律。在特征比較多的時(shí)候,可以指出哪些特征時(shí)關(guān)鍵的,而哪些特征時(shí)不重要的。

7. 本章小結(jié)

在回歸方程中,求得特征對(duì)應(yīng)的最佳回歸系數(shù)的方法是最小化誤差的平方差。給定輸入矩陣 X,如果 $X^TX$ 的逆存在并可以求得的話,回歸法對(duì)可以直接使用。數(shù)據(jù)集上計(jì)算出的回歸方程并不一定意味著它是最佳,可以使用預(yù)測(cè)值 yHat 和原始值 y 的相關(guān)性來度量回歸方程的好壞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容