導(dǎo)語
回歸:從一組數(shù)據(jù)出發(fā),確定某些變量之間的定量關(guān)系式;即建立數(shù)學(xué)模型并估計(jì)未知參數(shù)。
回歸的目的是預(yù)測(cè)數(shù)值型的目標(biāo)值,它的目標(biāo)是接受連續(xù)數(shù)據(jù),尋找最適合數(shù)據(jù)的方程,并能夠?qū)μ囟ㄖ颠M(jìn)行預(yù)測(cè)。這個(gè)方程稱為回歸方程,而求回歸方程顯然就是求該方程的回歸系數(shù),求這些回歸系數(shù)的過程就是回歸。
我們先來看看回歸跟分類的區(qū)別:(引用 - 走刀口的回答 - 知乎
https://www.zhihu.com/question/21329754/answer/17901883)
分類和回歸的區(qū)別在于輸出變量的類型。
定量輸出稱為回歸,或者說是連續(xù)變量預(yù)測(cè);
定性輸出稱為分類,或者說是離散變量預(yù)測(cè)。
舉個(gè)例子:
預(yù)測(cè)明天的氣溫是多少度,這是一個(gè)回歸任務(wù);
預(yù)測(cè)明天是陰、晴還是雨,就是一個(gè)分類任務(wù)。
普通線性回歸
我們做線性回歸是為了得到一個(gè)最優(yōu)回歸系數(shù)向量w使得當(dāng)我們給定一個(gè)x能夠通過y=xw預(yù)測(cè)y的值。假定輸入數(shù)據(jù)存放在矩陣 X 中,而回歸系數(shù)存在在向量 w 中。那么對(duì)于給定的數(shù)據(jù)X1,預(yù)測(cè)結(jié)果將會(huì)通過Y1=XT1w給出。
那么怎樣的w才是最優(yōu)的呢?在標(biāo)準(zhǔn)線性回歸中我們需要找到是誤差最小的w, 即預(yù)測(cè)的y值與真實(shí)的y值之間的差值,為了避免簡單累加造成的正負(fù)差值相互抵消,這里采用了平方誤差:

用矩陣可表示為:(y-Xw)T(y-Xw),)因?yàn)橐蠛瘮?shù)的極小值,對(duì)w求導(dǎo)可得:

使其等于0,便可求出W的最優(yōu)解:

上述求w最優(yōu)解的方法也叫做最小二乘法。
需要注意的是,上述公式中包含(XTX)-1,也就是需要對(duì)矩陣求逆,因此這個(gè)方程只有在逆矩陣存在的時(shí)候有用,所以我們寫代碼時(shí)必須需要事先確定矩陣是否可逆。
此外,我們知道線性回歸的方程的一般形式為:y=wx+b;即存在一定的偏移量b,于是,我們可以將回歸系數(shù)和特征向量均增加一個(gè)維度,將函數(shù)的偏移值b也算作回歸系數(shù)的一部分,占據(jù)回歸系數(shù)向量中的一個(gè)維度,比如w=(w1,w2,...,wN,b)。相應(yīng)地,將每條數(shù)據(jù)特征向量的第一個(gè)維度設(shè)置為1.0,即所有特征向量的第一個(gè)維度值均為1.0,這樣,最后得到的回歸函數(shù)形式為y=w[0]+w[1]x1+...+w[N]xN.
Python代碼實(shí)現(xiàn)
# 普通線性回歸
def standRegress(xArr, yArr):
# 用mat函數(shù)轉(zhuǎn)換為矩陣之后可以才進(jìn)行一些線性代數(shù)的操作
xMat = mat(xArr); yMat = mat(yArr).T
# XtX
xTx = xMat.T * xMat
# 判斷矩陣是否可逆
if linalg.det(xTx) == 0:
print("This matrix is singular, cannot do inverse")
return
# 根據(jù)公式計(jì)算w
w_hat = xTx.I * (xMat.T * yMat)
return w_hat

相關(guān)系數(shù)(Correlation Coefficient)計(jì)算:
幾乎任意數(shù)據(jù)集都可以用上述方法建立模型,怎么判斷這些模型的好壞呢?為了計(jì)算預(yù)測(cè)值序列和真實(shí)值序列的匹配程度,可以計(jì)算出這兩個(gè)序列的相關(guān)系數(shù)。
計(jì)算公式:

公式就是用X、Y的協(xié)方差除以X的標(biāo)準(zhǔn)差和Y的標(biāo)準(zhǔn)差。協(xié)方差是衡量兩個(gè)變量變化趨勢(shì)是否相似的一種方法,是同向變化(同時(shí)變大或變小)還是反向變化(一個(gè)變大一個(gè)變小), 同向或者反向的程度如何,計(jì)算公式如下:

在Python中,NumPy庫提供了相關(guān)系數(shù)的計(jì)算方法:
corrcoef(yEstimate, yActual)來計(jì)算相系數(shù),該方法返回一個(gè)矩陣,該矩陣包所有兩兩組合的相關(guān)系數(shù)。
從上面的結(jié)果圖可以看出,線性回歸的一個(gè)問題是可能會(huì)欠擬合,標(biāo)準(zhǔn)的線性回歸是一種最小均方差的無偏差估計(jì),在計(jì)算所有點(diǎn)的時(shí)候都是無偏差的計(jì)算誤差,如果針對(duì)不同的點(diǎn)能夠?qū)φ`差進(jìn)行調(diào)整便可以一定程度上避免標(biāo)準(zhǔn)線性回歸帶來的欠擬合現(xiàn)象。接下來介紹的局部加權(quán)線性回歸就是采取這種方法對(duì)值進(jìn)行預(yù)測(cè)。
局部加權(quán)線性回歸
在該算法中,我們給帶預(yù)測(cè)點(diǎn)附近的每個(gè)點(diǎn)賦予一定的權(quán)重,越靠近預(yù)測(cè)點(diǎn)的數(shù)據(jù)點(diǎn)分配的權(quán)重越高,于分類算法kNN一樣,此算法每次預(yù)測(cè)均需事先選取出對(duì)應(yīng)的數(shù)據(jù)子集,算法代價(jià)高。我們用θ表示回歸系數(shù),w表示權(quán)重, 那么平方誤差的表達(dá)式就變成:

用矩陣可表示為:

對(duì)θ求導(dǎo),令其等于0求極值得:

其中的W是一個(gè)矩陣,用來給每個(gè)數(shù)據(jù)點(diǎn)賦予權(quán)重。那我們?cè)趺从?jì)算這個(gè)W呢?
LWLR使用“核”(與支持向量機(jī)中的核類似)來對(duì)附近的點(diǎn)賦予更高的權(quán)重。核的類型可以自由選擇,最常用的是高斯核,高斯核對(duì)應(yīng)的權(quán)重如下:

通過公式可以看到如果xi距離x的距離越小,W(i)就會(huì)越大,其中參數(shù)k決定了權(quán)重的大小。k越大權(quán)重的差距就越小,k越小權(quán)重的差距就很大,僅有局部的點(diǎn)參與進(jìn)回歸系數(shù)的求取,其他距離較遠(yuǎn)的權(quán)重都趨近于零。如果k去進(jìn)入無窮大,所有的權(quán)重都趨近于1,W也就近似等于單位矩陣,局部加權(quán)線性回歸變成標(biāo)準(zhǔn)的無偏差線性回歸,會(huì)造成欠擬合的現(xiàn)象;當(dāng)k很小的時(shí)候,距離較遠(yuǎn)的樣本點(diǎn)無法參與回歸參數(shù)的求取,會(huì)造成過擬合的現(xiàn)象。
Python代碼實(shí)現(xiàn)
# 局部加權(quán)線性回歸
def lwlr(testPoint, xArr, yArr, k=1.0):
# 先把數(shù)組轉(zhuǎn)為矩陣,方便進(jìn)行線性代數(shù)操作
xMat = mat(xArr); yMat = mat(yArr).T
# shape():返回矩陣的維數(shù),是個(gè)數(shù)組
numLine = shape(xMat)[0]
# eye(num): 生成一個(gè)對(duì)角矩陣(對(duì)角線全為1,其它為0)
weights = mat(eye(numLine))
#計(jì)算權(quán)重
for i in range(numLine):
# x(i) - x
diffMat = testPoint - xMat[i, :]
weights[i, i] = exp(diffMat * diffMat.T / (-2 * k ** 2))
xTx = xMat.T * (weights * xMat)
if linalg.det(xTx == 0.0): #矩陣不可逆
print("This matrix ix singular, cannot do inverse")
return
# 根據(jù)公式求出回歸系數(shù)w
w = xTx.I * xMat.T * weights * yMat
return testPoint * w
def lwlrTest(testArr, xArr, yArr, k=1):
numLines = shape(testArr)[0]
# 初始化y值,全為0
yHat = zeros(numLines)
for i in range(numLines):
yHat[i] = lwlr(testArr[i], xArr, yArr, k)
return yHat



局部加權(quán)線性回歸也存在一個(gè)問題,對(duì)于每一個(gè)要預(yù)測(cè)的點(diǎn),都要重新依據(jù)整個(gè)數(shù)據(jù)集計(jì)算一個(gè)線性回歸模型出來,增加了很大的計(jì)算量,使得算法代價(jià)極高。從上圖可以看出當(dāng)k=0.01時(shí)可以得到一個(gè)很好的預(yù)測(cè)。但是下圖展示了當(dāng)k=0.01時(shí)(假定我們預(yù)測(cè)的是x = 0.5這個(gè)點(diǎn)),每個(gè)點(diǎn)所占的權(quán)重:

可以發(fā)現(xiàn)除了預(yù)測(cè)點(diǎn)附近,大多數(shù)數(shù)據(jù)點(diǎn)的權(quán)重都接近于零,如果避免這些計(jì)算將可以減少程序運(yùn)行時(shí)間,從而緩解因計(jì)算量增加所帶來的問題。
嶺回歸
如果數(shù)據(jù)的特征比樣本點(diǎn)還多應(yīng)該怎么辦?或者說輸入的數(shù)據(jù)矩陣不是滿秩矩陣,是否還能用前面介紹的普通回歸或者局部加權(quán)回歸來做預(yù)測(cè)?我們?cè)谇蠡貧w系數(shù)的時(shí)候需要算出(XTX)-1,當(dāng)矩陣不是滿秩矩陣的話是不能進(jìn)行求逆運(yùn)算的,所以之前的回歸方法就不能用了,為了解決這個(gè)問題,我們需要對(duì)最初的標(biāo)準(zhǔn)線性回歸做一定的變化使原先無法求逆的矩陣變得非奇異,使得問題可以穩(wěn)定求解。我們可以通過縮減系數(shù)的方式來處理這些問題,例如嶺回歸和LASSO.
簡單的說,嶺回歸就是在矩陣(XTX)上加上一個(gè)λI從而使得矩陣非奇異,進(jìn)而能對(duì)(XTX) + λI求逆。其中矩陣I是一個(gè)單位矩陣,此時(shí)回歸系數(shù)的計(jì)算公式將變?yōu)椋?br>

Python代碼實(shí)現(xiàn)
# 嶺回歸
def ridgeRegress(xMat, yMat, lam=0.2):
xTx = xMat.T * xMat
# print(shape(xTx))
# print(shape(xMat))
temp = xTx + eye(shape(xTx)[0]) * lam
if linalg.det(temp) == 0.0:
print("This matrix is singular, cannot do reverse")
return
w = temp.I * (xMat.T * yMat)
return w
很明顯,我們要找到使預(yù)測(cè)誤差最小的λ,不同的λ可以得到不同的參數(shù)w,因此我們可以改變?chǔ)说闹祦淼玫綆X回歸系數(shù)的變化,通過嶺跡圖我們可以觀察較佳的λ取值:
def ridgeTest(xArr, yArr):
xMat = mat(xArr)
yMat = mat(yArr).T
xMean = mean(xMat, 0)
yMean = mean(yMat, 0)
yMat = yMat - yMean
#數(shù)據(jù)標(biāo)注化:所有特征減去各自的均值并除以方差
xMat = (xMat - xMean) / var(xMat, 0)
numTestPts = 30
wMat = zeros((numTestPts, shape(xMat)[1]))
for i in range(numTestPts):
ws = ridgeRegress(xMat, yMat, exp(i-10))
wMat[i, :] = ws.T
return wMat
上述代碼可以生成一個(gè)30行的回歸系數(shù),將其繪制出來可得到嶺跡圖:

該圖繪出了回歸系數(shù)與log(λ)的關(guān)系,在最左邊λ系數(shù)最小時(shí),可以得到所有系數(shù)的原始值(與標(biāo)準(zhǔn)線性回歸相同); 而在右邊,系數(shù)全部縮減為0, 從不穩(wěn)定趨于穩(wěn)定;為了定量的找到最佳參數(shù)值,還需要進(jìn)行交叉驗(yàn)證。要判斷哪些變量對(duì)結(jié)果的預(yù)測(cè)最具影響力,可以觀察他們的系數(shù)大小即可。
中心化和標(biāo)準(zhǔn)化
在回歸問題和一些機(jī)器學(xué)習(xí)算法中通常要對(duì)原始數(shù)據(jù)進(jìn)行中心化和標(biāo)準(zhǔn)化處理,也就是需要將數(shù)據(jù)的均值調(diào)整到0,標(biāo)準(zhǔn)差調(diào)整為1, 計(jì)算過程很簡單就是將所有數(shù)據(jù)減去平均值后再除以標(biāo)準(zhǔn)差,上述代碼中就是先對(duì)數(shù)據(jù)進(jìn)行了標(biāo)準(zhǔn)化處理,之所以需要進(jìn)行中心化其實(shí)就是個(gè)平移過程,將所有數(shù)據(jù)的中心平移到原點(diǎn)。而標(biāo)準(zhǔn)化則是使得所有數(shù)據(jù)的不同特征都有相同的尺度Scale, 這樣在使用梯度下降法以及其他方法優(yōu)化的時(shí)候不同特征參數(shù)的影響程度就會(huì)一致了。
交叉驗(yàn)證
為了定量的找到嶺回歸最佳參數(shù)值λ,還需要進(jìn)行交叉驗(yàn)證。一般地,交叉驗(yàn)證就是通過把數(shù)據(jù)集分成若干等份,每一份輪流作測(cè)試集,其余數(shù)據(jù)作訓(xùn)練集進(jìn)行輪流訓(xùn)練與測(cè)試。如果把數(shù)據(jù)集分成n份,就叫n-folds交叉驗(yàn)證,這樣會(huì)得到n個(gè)錯(cuò)誤率,然后取這n個(gè)的平均值作為最終的結(jié)果。
# 交叉驗(yàn)證
def crossValidation(xArr, yArr, numSplit):
# 誤差矩陣,每行有30個(gè)lam得到的結(jié)果
errorMat = zeros((numSplit, 30))
data_len = len(yArr)
indexList = list(range(len(yArr)))
for i in range(numSplit):
trainX = []
trainY = []
testX = []
testY = []
# 打亂列表順序,獲得隨機(jī)效果
random.shuffle(indexList)
# 劃分出訓(xùn)練集和測(cè)試集
for j in range(data_len):
if j < 0.9 * data_len:
trainX.append(xArr[indexList[j]])
trainY.append(yArr[indexList[j]])
else:
testX.append(xArr[indexList[j]])
testY.append(yArr[indexList[j]])
# 求出此次的回歸系數(shù) 30 * 8
wMat = ridgeTest(trainX, trainY)
# 求每一組回歸系數(shù)的誤差
for group in range(30):
testXMat = mat(testX)
trainXMat = mat(trainX)
trainXMean = mean(trainX, 0)
trainXVar = var(trainXMat, 0)
# 訓(xùn)練集做了標(biāo)準(zhǔn)化處理,測(cè)試集也要做相同處理
testXMat = (testXMat - trainXMean) / trainXVar
trainYMat = mat(trainY)
trainYMean = mean(trainYMat, 0)
yEst = testX * mat(wMat[group, :]).T + trainYMean
errorMat[i, group] = rssError(yEst.T.A, array(testY))
#計(jì)算每個(gè)lamda的平均誤差
meanErrors = mean(errorMat, 0)
minErrors = float(min(meanErrors))
bestWeightIndex = nonzero(meanErrors == minErrors)
print(bestWeightIndex[0])
LASSO回歸
LASSO(The Least Absolute Shrinkage and Selection Operator)是另一種縮減方法,將回歸系數(shù)收縮在一定的區(qū)域內(nèi)。LASSO的主要思想是構(gòu)造一個(gè)一階懲罰函數(shù)獲得一個(gè)精煉的模型, 通過最終確定一些變量的系數(shù)為0進(jìn)行特征篩選。
LASSO的懲罰項(xiàng)為:

而嶺回歸的懲罰項(xiàng):

我們可以看到,它倆唯一的不同點(diǎn)在于,這個(gè)約束條件使用絕對(duì)值代替了平方和。雖然約束形式只是稍作變化,但是相比嶺回歸可以直接通過矩陣運(yùn)算得到回歸系數(shù)相比,這細(xì)微的變化卻極大的增加了計(jì)算復(fù)雜度。下面介紹更為簡單的方法來得到與之差不多的結(jié)果。
前向逐步回歸
前向逐步回歸是一種貪心算法,即每一步都盡可能的減少誤差。一開始,所有的權(quán)重都設(shè)為1,然后每一步所做的決策是對(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
python代碼實(shí)現(xiàn)
def stageWise(xArr, yArr, eps=0.01, numIt=100):
xMat = mat(xArr)
yMat = mat(yArr).T
# 標(biāo)準(zhǔn)化處理
yMat -= mean(yMat, 0)
xMat = (xMat - mean(xMat, 0)) / var(xMat, 0)
ws = zeros((shape(xMat)[1], 1))
returnMat = zeros((numIt, shape(xMat)[1]))
# 每輪迭代
for i in range(numIt):
lowestError = inf
# 對(duì)每個(gè)特征
for j in range(shape(xMat)[1]):
# 增大縮小
for sign in [-1, 1]:
wsTest = ws.copy()
wsTest[j] += eps * sign
yEst = xMat * wsTest
rssE = rssError(yMat.A, yEst.A)
if rssE < lowestError:
lowestError = rssE
wsMax = wsTest
ws = wsMax.copy()
returnMat[i, :] = ws.T
return returnMat
我們?nèi)〔介L為0.01,迭代200次來看看結(jié)果:

我們可以看到結(jié)果中w1和w6都是0,這說明他們對(duì)目標(biāo)值的預(yù)測(cè)沒有任何影響,也就是說這些特征可能是不需要的。另外,一段時(shí)間后系數(shù)就已經(jīng)飽和并在特征值之間來回震蕩,這是應(yīng)為步長太大的緣故。我們可以看到第一個(gè)權(quán)重在0.04和0.05之間來回震蕩。說明最優(yōu)的權(quán)重應(yīng)該是在0.04-0.05之間,這時(shí)我們應(yīng)該把步長調(diào)小。但如果迭代次數(shù)過多,逐步線性回歸算法就會(huì)與常規(guī)的最小二乘法效果類似。使用0.005的步長并經(jīng)過1000次迭代后的結(jié)果圖:

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