文章目錄
- 迴歸(Regression) 概述
迴歸(Regression) 概述
- 我們前邊提到的分類的目標變量是標稱型數據,而回歸則是對連續型的數據做出處理,迴歸的目的是預測數值型數據的目標值。
迴歸 場景
迴歸的目的是預測數值型的目標值。最直接的辦法是依據輸入寫出一個目標值的計算公式。
假如你想要預測蘭博基尼跑車的功率大小,可能會這樣計算:
HorsePower = 0.0015 * annualSalary - 0.99 * hoursListeningToPublicRadio
這就是所謂的 迴歸方程(regression equation)
,其中的 0.0015 和 -0.99 稱作 迴歸係數(regression weights)
,求這些迴歸係數的過程就是迴歸。一旦有了這些迴歸係數,再給定輸入,做預測就非常容易了。具體的做法是用迴歸係數乘以輸入值,再將結果全部加在一起,就得到了預測值。我們這裏所說的,迴歸係數是一個向量,輸入也是向量,這些運算也就是求出二者的內積。
說到迴歸,一般都是指 線性迴歸(linear regression)
。線性迴歸意味着可以將輸入項分別乘以一些常量,再將結果加起來得到輸出。
補充:
線性迴歸假設特徵和結果滿足線性關係。其實線性關係的表達能力非常強大,每個特徵對結果的影響強弱可以由前面的參數體現,而且每個特徵變量可以首先映射到一個函數,然後再參與線性計算。這樣就可以表達特徵與結果之間的非線性關係。
迴歸 原理
1、線性迴歸
我們應該怎樣從一大堆數據裏求出迴歸方程呢? 假定輸入數據存放在矩陣 x 中,而回歸係數存放在向量 w 中。那麼對於給定的數據 X1,預測結果將會通過給出。現在的問題是,手裏有一些 X 和對應的 y,怎樣才能找到 w 呢?一個常用的方法就是找出使誤差最小的 w 。這裏的誤差是指預測 y 值和真實 y 值之間的差值,使用該誤差的簡單累加將使得正差值和負差值相互抵消,所以我們採用平方誤差(實際上就是我們通常所說的最小二乘法)。
平方誤差可以寫做(其實我們是使用這個函數作爲 loss function):
用矩陣表示還可以寫做
如果對 w 求導,得到,令其等於零,解出 w 如下(具體求導過程爲: http://blog.csdn.net/nomadlx53/article/details/50849941 ):
1.1、線性迴歸 須知概念
1.1.1、矩陣求逆
因爲我們在計算迴歸方程的迴歸係數時,用到的計算公式如下:
需要對矩陣求逆,因此這個方程只在逆矩陣存在的時候適用,我們在程序代碼中對此作出判斷。 判斷矩陣是否可逆的一個可選方案是:
判斷矩陣的行列式是否爲 0,若爲 0 ,矩陣就不存在逆矩陣,不爲 0 的話,矩陣才存在逆矩陣。
1.1.2、最小二乘法
最小二乘法(又稱最小平方法)是一種數學優化技術。它通過最小化誤差的平方和尋找數據的最佳函數匹配。
1.2、線性迴歸 工作原理
讀入數據,將數據特徵x、特徵標籤y存儲在矩陣x、y中
驗證 x^Tx 矩陣是否可逆
使用最小二乘法求得 迴歸係數 w 的最佳估計
1.3、線性迴歸 開發流程
收集數據: 採用任意方法收集數據
準備數據: 迴歸需要數值型數據,標稱型數據將被轉換成二值型數據
分析數據: 繪出數據的可視化二維圖將有助於對數據做出理解和分析,在採用縮減法求得新迴歸係數之後,可以將新擬合線繪在圖上作爲對比
訓練算法: 找到迴歸係數
測試算法: 使用 R^2 或者預測值和數據的擬合度,來分析模型的效果
使用算法: 使用迴歸,可以在給定輸入的時候預測出一個數值,這是對分類方法的提升,因爲這樣可以預測連續型數據而不僅僅是離散的類別標籤
1.4、線性迴歸 算法特點
優點:結果易於理解,計算上不復雜。
缺點:對非線性的數據擬合不好。
適用於數據類型:數值型和標稱型數據。
1.5、線性迴歸 項目案例
1.5.1、線性迴歸 項目概述
根據下圖中的點,找出該數據的最佳擬合直線。
數據格式爲:
x0 x1 y
1.000000 0.067732 3.176513
1.000000 0.427810 3.816464
1.000000 0.995731 4.550095
1.000000 0.738336 4.256571
1.5.2、線性迴歸 編寫代碼
# 線性迴歸
import numpy as np
import matplotlib.pyplot as plt
def loadDataSet(fileName:str):
""" 加載數據
解析以tab鍵分隔的文件中的浮點數
Returns:
dataMat : feature 對應的數據集
labelMat : feature 對應的分類標籤,即類別標籤
"""
# 獲取樣本特徵的總數,不算最後的目標變量
numFeat = len(open(fileName).readline().split('\t')) - 1
dataMat = []
labelMat = []
fr = open(fileName)
for line in fr.readlines():
# 讀取每一行
lineArr =[]
# 刪除一行中以tab分隔的數據前後的空白符號
curLine = line.strip().split('\t')
# i 從0到2,不包括2
for i in range(numFeat):
# 將數據添加到lineArr List中,每一行數據測試數據組成一個行向量
lineArr.append(float(curLine[i]))
# 將測試數據的輸入數據部分存儲到dataMat 的List中
dataMat.append(lineArr)
# 將每一行的最後一個數據,即類別,或者叫目標變量存儲到labelMat List中
labelMat.append(float(curLine[-1]))
return dataMat,labelMat
def standRegres(xArr,yArr):
'''
Description:
線性迴歸
Args:
xArr :輸入的樣本數據,包含每個樣本數據的 feature
yArr :對應於輸入數據的類別標籤,也就是每個樣本對應的目標變量
Returns:
ws:迴歸係數
'''
# mat()函數將xArr,yArr轉換爲矩陣 mat().T 代表的是對矩陣進行轉置操作
xMat = np.mat(xArr)
yMat = np.mat(yArr).T
# print(yMat.shape)
# 矩陣乘法的條件是左矩陣的列數等於右矩陣的行數
xTx = xMat.T*xMat
# 因爲要用到xTx的逆矩陣,所以事先需要確定計算得到的xTx是否可逆,條件是矩陣的行列式不爲0
# linalg.det() 函數是用來求得矩陣的行列式的,如果矩陣的行列式爲0,則這個矩陣是不可逆的,就無法進行接下來的運算
if np.linalg.det(xTx) == 0.0:
print("This matrix is singular, cannot do inverse")
return
# 最小二乘法
# http://cwiki.apachecn.org/pages/viewpage.action?pageId=5505133
# 書中的公式,求得w的最優解
ws = xTx.I * (xMat.T*yMat)
return ws
# X,Y = loadDataSet(r'/home/ach/桌面/machine-learning/ai/Regression/8.Regression/data.txt')
# #print(X)# x是一個兩列標籤的數據
# W = standRegres(X,Y)
# print(W)
def regression1():
xArr, yArr = loadDataSet(r'/home/ach/桌面/machine-learning/ai/Regression/8.Regression/data.txt')
xMat = np.mat(xArr)
yMat = np.mat(yArr)
ws = standRegres(xArr, yArr)
fig = plt.figure()
ax = fig.add_subplot(111) # add_subplot(349)函數的參數的意思是,將畫布分成3行4列圖像畫在從左到右從上到下第9塊
ax.scatter([xMat[:, 1].flatten()], [yMat.T[:, 0].flatten().A[0]]) # scatter 的x是xMat中的第二列,y是yMat的第一列
xCopy = xMat.copy()
xCopy.sort(0)
yHat = xCopy * ws
ax.plot(xCopy[:, 1], yHat)
plt.show()
# 這裏也並沒有將數據全部顯示出來
regression1()
1.5.3、線性迴歸 擬合效果
2、局部加權線性迴歸
線性迴歸的一個問題是有可能出現欠擬合現象,因爲它求的是具有最小均方差的無偏估計。顯而易見,如果模型欠擬合將不能取得最好的預測效果。所以有些方法允許在估計中引入一些偏差,從而降低預測的均方誤差。
一個方法是局部加權線性迴歸(Locally Weighted Linear Regression,LWLR)。在這個算法中,我們給預測點附近的每個點賦予一定的權重,然後與 線性迴歸 類似,在這個子集上基於最小均方誤差來進行普通的迴歸。我們需要最小化的目標函數大致爲形式
目標函數中 w 爲權重,不是迴歸係數。與 kNN 一樣,這種算法每次預測均需要事先選取出對應的數據子集。該算法解出迴歸係數 w 的形式如下:
其中 W 是一個矩陣,用來給每個數據點賦予權重。 則爲迴歸係數。 這兩個是不同的概念,請勿混用。
LWLR 使用 “核”(與支持向量機中的核類似)來對附近的點賦予更高的權重。核的類型可以自由選擇,最常用的核就是高斯核,高斯覈對應的權重如下:
這樣就構建了一個只含對角元素的權重矩陣 w,並且點 x 與 x(i) 越近,w(i) 將會越大。上述公式中包含一個需要用戶指定的參數 k ,它決定了對附近的點賦予多大的權重,這也是使用 LWLR 時唯一需要考慮的參數,下面的圖給出了參數 k 與權重的關係。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-G5T0YLR0-1580216816595)(/home/ach/.config/Typora/typora-user-images/image-20200128204542596.png)]
上面的圖是 每個點的權重圖(假定我們正預測的點是 x = 0.5),最上面的圖是原始數據集,第二個圖顯示了當 k = 0.5 時,大部分的數據都用於訓練迴歸模型;而最下面的圖顯示當 k=0.01 時,僅有很少的局部點被用於訓練迴歸模型。
2.1、局部加權線性迴歸 工作原理
讀入數據,將數據特徵x、特徵標籤y存儲在矩陣x、y中
利用高斯核構造一個權重矩陣 W,對預測點附近的點施加權重
驗證 X^TWX 矩陣是否可逆
使用最小二乘法求得 迴歸係數 w 的最佳估計
2.2、局部加權線性迴歸 項目案例
2.2.1、局部加權線性迴歸 項目概述
我們仍然使用上面 線性迴歸 的數據集,對這些點進行一個 局部加權線性迴歸 的擬合。
數據格式爲:
1.000000 0.067732 3.176513
1.000000 0.427810 3.816464
1.000000 0.995731 4.550095
1.000000 0.738336 4.256571
2.2.2、局部加權線性迴歸 編寫代碼
from numpy import *
import matplotlib.pyplot as plt
def loadDataSet(fileName:str):
""" 加載數據
解析以tab鍵分隔的文件中的浮點數
Returns:
dataMat : feature 對應的數據集
labelMat : feature 對應的分類標籤,即類別標籤
"""
# 獲取樣本特徵的總數,不算最後的目標變量
numFeat = len(open(fileName).readline().split('\t')) - 1
dataMat = []
labelMat = []
fr = open(fileName)
for line in fr.readlines():
# 讀取每一行
lineArr =[]
# 刪除一行中以tab分隔的數據前後的空白符號
curLine = line.strip().split('\t')
# i 從0到2,不包括2
for i in range(numFeat):
# 將數據添加到lineArr List中,每一行數據測試數據組成一個行向量
lineArr.append(float(curLine[i]))
# 將測試數據的輸入數據部分存儲到dataMat 的List中
dataMat.append(lineArr)
# 將每一行的最後一個數據,即類別,或者叫目標變量存儲到labelMat List中
labelMat.append(float(curLine[-1]))
return dataMat,labelMat
# 局部加權線性迴歸只是針對某一個待測點進行的預測
def lwlr(testPoint, xArr, yArr, k=1.0):
'''
Description:
局部加權線性迴歸,在待預測點附近的每個點賦予一定的權重,在子集上基於最小均方差來進行普通的迴歸。
Args:
testPoint:樣本點
xArr:樣本的特徵數據,即 feature
yArr:每個樣本對應的類別標籤,即目標變量
k:關於賦予權重矩陣的核的一個參數,與權重的衰減速率有關
Returns:
testPoint * ws:數據點與具有權重的係數相乘得到的預測點
Notes:
這其中會用到計算權重的公式,w = e^((x^((i))-x) / -2k^2)
理解:x爲某個預測點,x^((i))爲樣本點,樣本點距離預測點越近,貢獻的誤差越大(權值越大),越遠則貢獻的誤差越小(權值越小)。
關於預測點的選取,在我的代碼中取的是樣本點。其中k是帶寬參數,控制w(鐘形函數)的寬窄程度,類似於高斯函數的標準差。
算法思路:假設預測點取樣本點中的第i個樣本點(共m個樣本點),遍歷1到m個樣本點(含第i個),算出每一個樣本點與預測點的距離,
也就可以計算出每個樣本貢獻誤差的權值,可以看出w是一個有m個元素的向量(寫成對角陣形式)。
'''
# mat() 函數是將array轉換爲矩陣的函數, mat().T 是轉換爲矩陣之後,再進行轉置操作
xMat = mat(xArr)
yMat = mat(yArr).T
# 獲得xMat矩陣的行數
m = shape(xMat)[0]
# eye()返回一個對角線元素爲1,其他元素爲0的二維數組,創建權重矩陣weights,該矩陣爲每個樣本點初始化了一個權重
weights = mat(eye((m)))
for j in range(m):
# testPoint 的形式是 一個行向量的形式
# 計算 testPoint 與輸入樣本點之間的距離,然後下面計算出每個樣本貢獻誤差的權值
diffMat = testPoint - xMat[j, :]
# diffMat的維度是1*2那麼diffMat * diffMat.T就是一個數字
# k控制衰減的速度
weights[j, j] = exp(diffMat * diffMat.T / (-2.0 * k ** 2))
# 根據矩陣乘法計算 xTx ,其中的 weights 矩陣是樣本點對應的權重矩陣
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):
'''
Description:
測試局部加權線性迴歸,對數據集中每個點調用 lwlr() 函數
Args:
testArr:測試所用的所有樣本點
xArr:樣本的特徵數據,即 feature
yArr:每個樣本對應的類別標籤,即目標變量
k:控制核函數的衰減速率
Returns:
yHat:預測點的估計值
'''
# 得到樣本點的總數
m = shape(testArr)[0]
# 構建一個全部都是 0 的 1 * m 的矩陣
yHat = zeros(m)
# 循環所有的數據點,並將lwlr運用於所有的數據點
for i in range(m):
yHat[i] = lwlr(testArr[i], xArr, yArr, k)
# 返回估計值
return yHat
def regression2():
xArr, yArr = loadDataSet(r'/home/ach/桌面/machine-learning/ai/Regression/8.Regression/data.txt')
yHat = lwlrTest(xArr, xArr, yArr, 0.01)
xMat = mat(xArr)
srtInd = xMat[:, 1].argsort(0) # argsort()函數是將x中的元素從小到大排列,提取其對應的index(索引),然後輸出
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]], [mat(yArr).T.flatten().A[0]], s=2, c='red')
plt.show()
regression2()
2.2.3、局部加權線性迴歸 擬合效果
經過試驗,發現k=0.1的時候是最好的,擬合效果最好
2.3、局部加權線性迴歸 注意事項
局部加權線性迴歸也存在一個問題,即增加了計算量,因爲它對每個點做預測時都必須使用整個數據集。
3、線性迴歸 & 局部加權線性迴歸 項目案例
到此爲止,我們已經介紹了找出最佳擬合直線的兩種方法,下面我們用這些技術來預測鮑魚的年齡。
3.1、項目概述
我們有一份來自 UCI 的數據集合的數據,記錄了鮑魚(一種介殼類水生動物)的年齡。鮑魚年齡可以從鮑魚殼的層數推算得到。
3.2、開發流程
收集數據: 採用任意方法收集數據
準備數據: 迴歸需要數值型數據,標稱型數據將被轉換成二值型數據
分析數據: 繪出數據的可視化二維圖將有助於對數據做出理解和分析,在採用縮減法求得新迴歸係數之後,可以將新擬合線繪在圖上作爲對比
訓練算法: 找到迴歸係數
測試算法: 使用 rssError()函數 計算預測誤差的大小,來分析模型的效果
使用算法: 使用迴歸,可以在給定輸入的時候預測出一個數值,這是對分類方法的提升,因爲這樣可以預測連續型數據而不僅僅是離散的類別標籤
收集數據: 採用任意方法收集數據
準備數據: 迴歸需要數值型數據,標稱型數據將被轉換成二值型數據
數據存儲格式:
1 0.455 0.365 0.095 0.514 0.2245 0.101 0.15 15
1 0.35 0.265 0.09 0.2255 0.0995 0.0485 0.07 7
-1 0.53 0.42 0.135 0.677 0.2565 0.1415 0.21 9
1 0.44 0.365 0.125 0.516 0.2155 0.114 0.155 10
0 0.33 0.255 0.08 0.205 0.0895 0.0395 0.055 7
分析數據: 繪出數據的可視化二維圖將有助於對數據做出理解和分析,在採用縮減法求得新迴歸係數之後,可以將新擬合線繪在圖上作爲對比
訓練算法: 找到迴歸係數
使用上面我們講到的 局部加權線性迴歸 訓練算法,求出迴歸係數
測試算法: 使用 rssError()函數 計算預測誤差的大小,來分析模型的效果
from numpy import *
import numpy as np
import matplotlib.pyplot as plt
def loadDataSet(fileName:str):
""" 加載數據
解析以tab鍵分隔的文件中的浮點數
Returns:
dataMat : feature 對應的數據集
labelMat : feature 對應的分類標籤,即類別標籤
"""
# 獲取樣本特徵的總數,不算最後的目標變量
numFeat = len(open(fileName).readline().split('\t')) - 1
dataMat = []
labelMat = []
fr = open(fileName)
for line in fr.readlines():
# 讀取每一行
lineArr =[]
# 刪除一行中以tab分隔的數據前後的空白符號
curLine = line.strip().split('\t')
# i 從0到2,不包括2
for i in range(numFeat):
# 將數據添加到lineArr List中,每一行數據測試數據組成一個行向量
lineArr.append(float(curLine[i]))
# 將測試數據的輸入數據部分存儲到dataMat 的List中
dataMat.append(lineArr)
# 將每一行的最後一個數據,即類別,或者叫目標變量存儲到labelMat List中
labelMat.append(float(curLine[-1]))
return dataMat,labelMat
# 局部加權線性迴歸只是針對某一個待測點進行的預測
def lwlr(testPoint, xArr, yArr, k=1.0):
'''
Description:
局部加權線性迴歸,在待預測點附近的每個點賦予一定的權重,在子集上基於最小均方差來進行普通的迴歸。
Args:
testPoint:樣本點
xArr:樣本的特徵數據,即 feature
yArr:每個樣本對應的類別標籤,即目標變量
k:關於賦予權重矩陣的核的一個參數,與權重的衰減速率有關
Returns:
testPoint * ws:數據點與具有權重的係數相乘得到的預測點
Notes:
這其中會用到計算權重的公式,w = e^((x^((i))-x) / -2k^2)
理解:x爲某個預測點,x^((i))爲樣本點,樣本點距離預測點越近,貢獻的誤差越大(權值越大),越遠則貢獻的誤差越小(權值越小)。
關於預測點的選取,在我的代碼中取的是樣本點。其中k是帶寬參數,控制w(鐘形函數)的寬窄程度,類似於高斯函數的標準差。
算法思路:假設預測點取樣本點中的第i個樣本點(共m個樣本點),遍歷1到m個樣本點(含第i個),算出每一個樣本點與預測點的距離,
也就可以計算出每個樣本貢獻誤差的權值,可以看出w是一個有m個元素的向量(寫成對角陣形式)。
'''
# mat() 函數是將array轉換爲矩陣的函數, mat().T 是轉換爲矩陣之後,再進行轉置操作
xMat = mat(xArr)
yMat = mat(yArr).T
# 獲得xMat矩陣的行數
m = shape(xMat)[0]
# eye()返回一個對角線元素爲1,其他元素爲0的二維數組,創建權重矩陣weights,該矩陣爲每個樣本點初始化了一個權重
weights = mat(eye((m)))
for j in range(m):
# testPoint 的形式是 一個行向量的形式
# 計算 testPoint 與輸入樣本點之間的距離,然後下面計算出每個樣本貢獻誤差的權值
diffMat = testPoint - xMat[j, :]
# diffMat的維度是1*2那麼diffMat * diffMat.T就是一個數字
# k控制衰減的速度
weights[j, j] = exp(diffMat * diffMat.T / (-2.0 * k ** 2))
# 根據矩陣乘法計算 xTx ,其中的 weights 矩陣是樣本點對應的權重矩陣
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):
'''
Description:
測試局部加權線性迴歸,對數據集中每個點調用 lwlr() 函數
Args:
testArr:測試所用的所有樣本點
xArr:樣本的特徵數據,即 feature
yArr:每個樣本對應的類別標籤,即目標變量
k:控制核函數的衰減速率
Returns:
yHat:預測點的估計值
'''
# 得到樣本點的總數
m = shape(testArr)[0]
# 構建一個全部都是 0 的 1 * m 的矩陣
yHat = zeros(m)
# 循環所有的數據點,並將lwlr運用於所有的數據點
for i in range(m):
yHat[i] = lwlr(testArr[i], xArr, yArr, k)
# 返回估計值
return yHat
# def regression2():
# xArr, yArr = loadDataSet(r'/home/ach/桌面/machine-learning/ai/Regression/8.Regression/data.txt')
# yHat = lwlrTest(xArr, xArr, yArr, 0.01)
# xMat = mat(xArr)
# srtInd = xMat[:, 1].argsort(0) # argsort()函數是將x中的元素從小到大排列,提取其對應的index(索引),然後輸出
# 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]], [mat(yArr).T.flatten().A[0]], s=2, c='red')
# plt.show()
# regression2()
def rssError(yArr,yHatArr):
return ((yArr-yHatArr)**2).sum()
def standRegres(xArr,yArr):
'''
Description:
線性迴歸
Args:
xArr :輸入的樣本數據,包含每個樣本數據的 feature
yArr :對應於輸入數據的類別標籤,也就是每個樣本對應的目標變量
Returns:
ws:迴歸係數
'''
# mat()函數將xArr,yArr轉換爲矩陣 mat().T 代表的是對矩陣進行轉置操作
xMat = np.mat(xArr)
yMat = np.mat(yArr).T
# print(yMat.shape)
# 矩陣乘法的條件是左矩陣的列數等於右矩陣的行數
xTx = xMat.T*xMat
# 因爲要用到xTx的逆矩陣,所以事先需要確定計算得到的xTx是否可逆,條件是矩陣的行列式不爲0
# linalg.det() 函數是用來求得矩陣的行列式的,如果矩陣的行列式爲0,則這個矩陣是不可逆的,就無法進行接下來的運算
if np.linalg.det(xTx) == 0.0:
print("This matrix is singular, cannot do inverse")
return
# 最小二乘法
# http://cwiki.apachecn.org/pages/viewpage.action?pageId=5505133
# 書中的公式,求得w的最優解
ws = xTx.I * (xMat.T*yMat)
return ws
# test for abloneDataSet
def abaloneTest():
'''
Desc:
預測鮑魚的年齡
Args:
None
Returns:
None
'''
# 加載數據
abX, abY = loadDataSet(r'/home/ach/桌面/machine-learning/ai/Regression/8.Regression/data.txt')
# 使用不同的核進行預測
oldyHat01 = lwlrTest(abX[0:99], abX[0:99], abY[0:99], 0.1)
oldyHat1 = lwlrTest(abX[0:99], abX[0:99], abY[0:99], 1)
oldyHat10 = lwlrTest(abX[0:99], abX[0:99], abY[0:99], 10)
# 打印出不同的核預測值與訓練數據集上的真實值之間的誤差大小
print("old yHat01 error Size is :", rssError(abY[0:99], oldyHat01.T))
print("old yHat1 error Size is :", rssError(abY[0:99], oldyHat1.T))
print("old yHat10 error Size is :", rssError(abY[0:99], oldyHat10.T))
# 打印出 不同的核預測值 與 新數據集(測試數據集)上的真實值之間的誤差大小
newyHat01 = lwlrTest(abX[100:199], abX[0:99], abY[0:99], 0.1)
print("new yHat01 error Size is :", rssError(abY[0:99], newyHat01.T))
newyHat1 = lwlrTest(abX[100:199], abX[0:99], abY[0:99], 1)
print("new yHat1 error Size is :", rssError(abY[0:99], newyHat1.T))
newyHat10 = lwlrTest(abX[100:199], abX[0:99], abY[0:99], 10)
print("new yHat10 error Size is :", rssError(abY[0:99], newyHat10.T))
# 使用簡單的 線性迴歸 進行預測,與上面的計算進行比較
standWs = standRegres(abX[0:99], abY[0:99])
standyHat = mat(abX[100:199]) * standWs
print("standRegress error Size is:", rssError(abY[100:199], standyHat.T.A))
abaloneTest()
根據我們上邊的測試,可以看出:
簡單線性迴歸達到了與局部加權現行迴歸類似的效果。這也說明了一點,必須在未知數據上比較效果才能選取到最佳模型。那麼最佳的核大小是 10 嗎?或許是,但如果想得到更好的效果,可以嘗試用 10 個不同的樣本集做 10 次測試來比較結果
運行結果:
使用算法: 使用迴歸,可以在給定輸入的時候預測出一個數值,這是對分類方法的提升,因爲這樣可以預測連續型數據而不僅僅是離散的類別標籤
4、縮減係數來 “理解” 數據
如果數據的特徵比樣本點還多應該怎麼辦?是否還可以使用線性迴歸和之前的方法來做預測?答案是否定的,即我們不能再使用前面介紹的方法。這是因爲在計算的時候會出錯。
如果特徵比樣本點還多(n > m),也就是說輸入數據的矩陣 x 不是滿秩矩陣。非滿秩矩陣求逆時會出現問題。
爲了解決這個問題,我們引入了 嶺迴歸(ridge regression)
這種縮減方法。接着是 lasso法
,最後介紹 前向逐步迴歸
。
4.1、嶺迴歸
簡單來說,嶺迴歸就是在矩陣上加一個 λI 從而使得矩陣非奇異,進而能對 求逆。其中矩陣I是一個 n * n (等於列數) 的單位矩陣, 對角線上元素全爲1,其他元素全爲0。而λ是一個用戶定義的數值,後面會做介紹。在這種情況下,迴歸係數的計算公式將變成:
嶺迴歸最先用來處理特徵數多於樣本數的情況,現在也用於在估計中加入偏差,從而得到更好的估計。這裏通過引入 λ 來限制了所有 w 之和,通過引入該懲罰項,能夠減少不重要的參數,這個技術在統計學中也叫作 縮減(shrinkage)
。
縮減方法可以去掉不重要的參數,因此能更好地理解數據。此外,與簡單的線性迴歸相比,縮減法能取得更好的預測效果。
這裏通過預測誤差最小化得到 λ: 數據獲取之後,首先抽一部分數據用於測試,剩餘的作爲訓練集用於訓練參數 w。訓練完畢後在測試集上測試預測性能。通過選取不同的 λ 來重複上述測試過程,最終得到一個使預測誤差最小的 λ 。
4.1.1、嶺迴歸 原始代碼
from numpy import *
def ridgeRegres(xMat, yMat, lam=0.2):
'''
Desc:
這個函數實現了給定 lambda 下的嶺迴歸求解。
如果數據的特徵比樣本點還多,就不能再使用上面介紹的的線性迴歸和局部現行迴歸了,因爲計算 (xTx)^(-1)會出現錯誤。
如果特徵比樣本點還多(n > m),也就是說,輸入數據的矩陣x不是滿秩矩陣。非滿秩矩陣在求逆時會出現問題。
爲了解決這個問題,我們下邊講一下:嶺迴歸,這是我們要講的第一種縮減方法。
Args:
xMat:樣本的特徵數據,即 feature
yMat:每個樣本對應的類別標籤,即目標變量,實際值
lam:引入的一個λ值,使得矩陣非奇異
Returns:
經過嶺迴歸公式計算得到的迴歸係數
'''
xTx = xMat.T * xMat
# 嶺迴歸就是在矩陣 xTx 上加一個 λI 從而使得矩陣非奇異,進而能對 xTx + λI 求逆
print(shape(xMat)[1])
denom = xTx + eye(shape(xMat)[1]) * lam
# 檢查行列式是否爲零,即矩陣是否可逆,行列式爲0的話就不可逆,不爲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):
'''
Desc:
函數 ridgeTest() 用於在一組 λ 上測試結果
Args:
xArr:樣本數據的特徵,即 feature
yArr:樣本數據的類別標籤,即真實數據
Returns:
wMat:將所有的迴歸係數輸出到一個矩陣並返回
'''
xMat = mat(xArr)
yMat = mat(yArr).T
# 計算Y的均值
yMean = mean(yMat, 0)
# Y的所有的特徵減去均值
yMat = yMat - yMean
# 標準化 x,計算 xMat 平均值
xMeans = mean(xMat, 0)
# 然後計算 X的方差
xVar = var(xMat, 0)
# 所有特徵都減去各自的均值併除以方差
xMat = (xMat - xMeans) / xVar
# 可以在 30 個不同的 lambda 下調用 ridgeRegres() 函數。
numTestPts = 30
# 創建30 * m 的全部數據爲0 的矩陣
wMat = zeros((numTestPts, shape(xMat)[1]))
for i in range(numTestPts):
# exp() 返回 e^x
ws = ridgeRegres(xMat, yMat, exp(i - 10))
wMat[i, :] = ws.T
return wMat
# test for ridgeRegression
import matplotlib.pyplot as plt
def loadDataSet(fileName:str):
""" 加載數據
解析以tab鍵分隔的文件中的浮點數
Returns:
dataMat : feature 對應的數據集
labelMat : feature 對應的分類標籤,即類別標籤
"""
# 獲取樣本特徵的總數,不算最後的目標變量
numFeat = len(open(fileName).readline().split('\t')) - 1
dataMat = []
labelMat = []
fr = open(fileName)
for line in fr.readlines():
# 讀取每一行
lineArr =[]
# 刪除一行中以tab分隔的數據前後的空白符號
curLine = line.strip().split('\t')
# i 從0到2,不包括2
for i in range(numFeat):
# 將數據添加到lineArr List中,每一行數據測試數據組成一個行向量
lineArr.append(float(curLine[i]))
# 將測試數據的輸入數據部分存儲到dataMat 的List中
dataMat.append(lineArr)
# 將每一行的最後一個數據,即類別,或者叫目標變量存儲到labelMat List中
labelMat.append(float(curLine[-1]))
return dataMat,labelMat
def regression3():
abX, abY = loadDataSet(r'/home/ach/桌面/machine-learning/ai/Regression/8.Regression/abalone.txt')
ridgeWeights = ridgeTest(abX, abY)
fig = plt.figure()
ax = fig.add_subplot(111)
# 畫出了幾個特徵在嶺迴歸中的參數
ax.plot(ridgeWeights)
plt.show()
regression3()
4.1.2、嶺迴歸在鮑魚數據集上的運行效果
上圖繪製出了迴歸係數與 log(λ) 的關係。在最左邊,即 λ 最小時,可以得到所有係數的原始值(與線性迴歸一致);而在右邊,係數全部縮減爲0;在中間部分的某值將可以取得最好的預測效果。爲了定量地找到最佳參數值,還需要進行交叉驗證。另外,要判斷哪些變量對結果預測最具有影響力,在上圖中觀察它們對應的係數大小就可以了。
4.2、套索方法(Lasso,The Least Absolute Shrinkage and Selection Operator)
在增加如下約束時,普通的最小二乘法迴歸會得到與嶺迴歸一樣的公式:
上式限定了所有迴歸係數的平方和不能大於 λ 。使用普通的最小二乘法迴歸在當兩個或更多的特徵相關時,可能會得到一個很大的正係數和一個很大的負係數。正是因爲上述限制條件的存在,使用嶺迴歸可以避免這個問題。
與嶺迴歸類似,另一個縮減方法lasso也對迴歸係數做了限定,對應的約束條件如下:
唯一的不同點在於,這個約束條件使用絕對值取代了平方和。雖然約束形式只是稍作變化,結果卻大相徑庭: 在 λ 足夠小的時候,一些係數會因此被迫縮減到 0.這個特性可以幫助我們更好地理解數據。
4.3、前向逐步迴歸
前向逐步迴歸算法可以得到與 lasso 差不多的效果,但更加簡單。它屬於一種貪心算法,即每一步都儘可能減少誤差。一開始,所有權重都設置爲 0,然後每一步所做的決策是對某個權重增加或減少一個很小的值。
僞代碼如下:
數據標準化,使其分佈滿足 0 均值 和單位方差
在每輪迭代過程中:
設置當前最小誤差 lowestError 爲正無窮
對每個特徵:
增大或縮小:
改變一個係數得到一個新的 w
計算新 w 下的誤差
如果誤差 Error 小於當前最小誤差 lowestError: 設置 Wbest 等於當前的 W
將 W 設置爲新的 Wbest
4.3.1、前向逐步迴歸 原始代碼
from numpy import *
def regularize(xMat): # 按列進行規範化
inMat = xMat.copy()
inMeans = mean(inMat, 0) # 計算平均值然後減去它
inVar = var(inMat, 0) # 計算除以Xi的方差
inMat = (inMat - inMeans) / inVar
return inMat
def stageWise(xArr, yArr, eps=0.01, numIt=100):
'''
:param xArr:
:param yArr:
:param eps:
:param numIt: 迭代次數
:return:
'''
xMat = mat(xArr)
yMat = mat(yArr).T
yMean = mean(yMat, 0)
yMat = yMat - yMean # 也可以規則化ys但會得到更小的coef
xMat = regularize(xMat)
m, n = shape(xMat)
returnMat = zeros((numIt, n)) # 測試代碼刪除
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
# test for stageWise
def rssError(yArr,yHatArr):
return ((yArr-yHatArr)**2).sum()
def loadDataSet(fileName:str):
""" 加載數據
解析以tab鍵分隔的文件中的浮點數
Returns:
dataMat : feature 對應的數據集
labelMat : feature 對應的分類標籤,即類別標籤
"""
# 獲取樣本特徵的總數,不算最後的目標變量
numFeat = len(open(fileName).readline().split('\t')) - 1
dataMat = []
labelMat = []
fr = open(fileName)
for line in fr.readlines():
# 讀取每一行
lineArr =[]
# 刪除一行中以tab分隔的數據前後的空白符號
curLine = line.strip().split('\t')
# i 從0到2,不包括2
for i in range(numFeat):
# 將數據添加到lineArr List中,每一行數據測試數據組成一個行向量
lineArr.append(float(curLine[i]))
# 將測試數據的輸入數據部分存儲到dataMat 的List中
dataMat.append(lineArr)
# 將每一行的最後一個數據,即類別,或者叫目標變量存儲到labelMat List中
labelMat.append(float(curLine[-1]))
return dataMat,labelMat
def regression4():
xArr, yArr = loadDataSet(r'/home/ach/桌面/machine-learning/ai/Regression/8.Regression/abalone.txt')
# stageWise(xArr, yArr, 0.01, 200)
print(stageWise(xArr, yArr, 0.01, 200)[199,])
regression4()
4.3.2、逐步線性迴歸在鮑魚數據集上的運行效果
逐步線性迴歸算法的主要優點在於它可以幫助人們理解現有的模型並作出改進。當構建了一個模型後,可以運行該算法找出重要的特徵,這樣就有可能及時停止對那些不重要特徵的收集。最後,如果用於測試,該算法每100次迭代後就可以構建出一個模型,可以使用類似於10折交叉驗證的方法比較這些模型,最終選擇使誤差最小的模型。
4.4、小結
當應用縮減方法(如逐步線性迴歸或嶺迴歸)時,模型也就增加了偏差(bias),與此同時卻減小了模型的方差。
5、權衡偏差和方差
任何時候,一旦發現模型和測量值之間存在差異,就說出現了誤差。當考慮模型中的 “噪聲” 或者說誤差時,必須考慮其來源。你可能會對複雜的過程進行簡化,這將導致在模型和測量值之間出現 “噪聲” 或誤差,若無法理解數據的真實生成過程,也會導致差異的產生。另外,測量過程本身也可能產生 “噪聲” 或者問題。下面我們舉一個例子,我們使用 線性迴歸
和 局部加權線性迴歸
處理過一個從文件導入的二維數據。
其中的 N(0, 1) 是一個均值爲 0、方差爲 1 的正態分佈。我們嘗試過僅用一條直線來擬合上述數據。不難想到,直線所能得到的最佳擬合應該是 3.0+1.7x 這一部分。這樣的話,誤差部分就是 0.1sin(30x)+0.06N(0, 1) 。在上面,我們使用了局部加權線性迴歸來試圖捕捉數據背後的結構。該結構擬合起來有一定的難度,因此我們測試了多組不同的局部權重來找到具有最小測試誤差的解。
下圖給出了訓練誤差和測試誤差的曲線圖,上面的曲面就是測試誤差,下面的曲線是訓練誤差。我們根據 預測鮑魚年齡 的實驗知道: 如果降低核的大小,那麼訓練誤差將變小。從下圖開看,從左到右就表示了核逐漸減小的過程。
一般認爲,上述兩種誤差由三個部分組成: 偏差、測量誤差和隨機噪聲。局部加權線性迴歸 和 預測鮑魚年齡 中,我們通過引入了三個越來越小的核來不斷增大模型的方差。
在縮減係數來“理解”數據這一節中,我們介紹了縮減法,可以將一些係數縮減成很小的值或直接縮減爲 0 ,這是一個增大模型偏差的例子。通過把一些特徵的迴歸係數縮減到 0 ,同時也就減小了模型的複雜度。例子中有 8 個特徵,消除其中兩個後不僅使模型更易理解,同時還降低了預測誤差。對照上圖,左側是參數縮減過於嚴厲的結果,而右側是無縮減的效果。
方差是可以度量的。如果從鮑魚數據中取一個隨機樣本集(例如取其中 100 個數據)並用線性模型擬合,將會得到一組迴歸係數。同理,再取出另一組隨機樣本集並擬合,將會得到另一組迴歸係數。這些係數間的差異大小也就是模型方差的反映。
6、迴歸 項目案例
項目案例1: 預測樂高玩具套裝的價格
項目概述
Dangler 喜歡爲樂高套裝估價,我們用迴歸技術來幫助他建立一個預測模型。
開發流程
(1) 收集數據:用 Google Shopping 的API收集數據。
(2) 準備數據:從返回的JSON數據中抽取價格。
(3) 分析數據:可視化並觀察數據。
(4) 訓練算法:構建不同的模型,採用逐步線性迴歸和直接的線性迴歸模型。
(5) 測試算法:使用交叉驗證來測試不同的模型,分析哪個效果最好。
(6) 使用算法:這次練習的目標就是生成數據模型。
收集數據: 使用 Google 購物的 API
由於 Google 提供的 api 失效,我們只能自己下載咯,將數據存儲在了 input 文件夾下的 setHtml 文件夾下
準備數據: 從返回的 JSON 數據中抽取價格
因爲我們這裏不是在線的,就不再是 JSON 了,我們直接解析線下的網頁,得到我們想要的數據。
分析數據: 可視化並觀察數據
這裏我們將解析得到的數據打印出來,然後觀察數據。
訓練算法: 構建不同的模型
from numpy import *
from bs4 import BeautifulSoup
import random
# 從頁面讀取數據,生成retX和retY列表
def scrapePage(retX, retY, inFile, yr, numPce, origPrc):
# 打開並讀取HTML文件
fr = open(inFile) # 這裏推薦使用with open() 生成器,這樣節省內存也可以避免最後忘記關閉文件的問題
soup = BeautifulSoup(fr.read())
i=1
# 根據HTML頁面結構進行解析
currentRow = soup.findAll('table', r="%d" % i)
while(len(currentRow)!=0):
currentRow = soup.findAll('table', r="%d" % i)
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', '')
sellingPrice = float(priceStr)
# 去掉不完整的套裝價格
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)
i += 1
currentRow = soup.findAll('table', r="%d" % i)
'''
# 依次讀取六種樂高套裝的數據,並生成數據矩陣
'''
def setDataCollect(retX, retY):
scrapePage(retX, retY,r'/home/ach/桌面/machine-learning/ai/Regression/8.Regression/setHtml/lego8288.html', 2006, 800, 49.99)
scrapePage(retX, retY, r'/home/ach/桌面/machine-learning/ai/Regression/8.Regression/setHtml/lego10030.html', 2002, 3096, 269.99)
scrapePage(retX, retY, r'/home/ach/桌面/machine-learning/ai/Regression/8.Regression/setHtml/lego10179.html', 2007, 5195, 499.99)
scrapePage(retX, retY, r'/home/ach/桌面/machine-learning/ai/Regression/8.Regression/setHtml/lego10181.html', 2007, 3428, 199.99)
scrapePage(retX, retY, r'/home/ach/桌面/machine-learning/ai/Regression/8.Regression/setHtml/lego10189.html', 2008, 5922, 299.99)
scrapePage(retX, retY, r'/home/ach/桌面/machine-learning/ai/Regression/8.Regression/setHtml/lego10196.html', 2009, 3263, 249.99)
def ridgeRegres(xMat, yMat, lam=0.2):
'''
Desc:
這個函數實現了給定 lambda 下的嶺迴歸求解。
如果數據的特徵比樣本點還多,就不能再使用上面介紹的的線性迴歸和局部現行迴歸了,因爲計算 (xTx)^(-1)會出現錯誤。
如果特徵比樣本點還多(n > m),也就是說,輸入數據的矩陣x不是滿秩矩陣。非滿秩矩陣在求逆時會出現問題。
爲了解決這個問題,我們下邊講一下:嶺迴歸,這是我們要講的第一種縮減方法。
Args:
xMat:樣本的特徵數據,即 feature
yMat:每個樣本對應的類別標籤,即目標變量,實際值
lam:引入的一個λ值,使得矩陣非奇異
Returns:
經過嶺迴歸公式計算得到的迴歸係數
'''
xTx = xMat.T * xMat
# 嶺迴歸就是在矩陣 xTx 上加一個 λI 從而使得矩陣非奇異,進而能對 xTx + λI 求逆
print(shape(xMat)[1])
denom = xTx + eye(shape(xMat)[1]) * lam
# 檢查行列式是否爲零,即矩陣是否可逆,行列式爲0的話就不可逆,不爲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):
'''
Desc:
函數 ridgeTest() 用於在一組 λ 上測試結果
Args:
xArr:樣本數據的特徵,即 feature
yArr:樣本數據的類別標籤,即真實數據
Returns:
wMat:將所有的迴歸係數輸出到一個矩陣並返回
'''
xMat = mat(xArr)
yMat = mat(yArr).T
# 計算Y的均值
yMean = mean(yMat, 0)
# Y的所有的特徵減去均值
yMat = yMat - yMean
# 標準化 x,計算 xMat 平均值
xMeans = mean(xMat, 0)
# 然後計算 X的方差
xVar = var(xMat, 0)
# 所有特徵都減去各自的均值併除以方差
xMat = (xMat - xMeans) / xVar
# 可以在 30 個不同的 lambda 下調用 ridgeRegres() 函數。
numTestPts = 30
# 創建30 * m 的全部數據爲0 的矩陣
wMat = zeros((numTestPts, shape(xMat)[1]))
for i in range(numTestPts):
# exp() 返回 e^x
ws = ridgeRegres(xMat, yMat, exp(i - 10))
wMat[i, :] = ws.T
return wMat
# 交叉驗證測試嶺迴歸
def crossValidation(xArr,yArr,numVal=10):
# 獲得數據點個數,xArr和yArr具有相同長度
m = len(yArr)
indexList = list(range(m))
errorMat = zeros((numVal,30))
# 主循環 交叉驗證循環
for i in range(numVal):
# 隨機拆分數據,將數據分爲訓練集(90%)和測試集(10%)
trainX=[]; trainY=[]
testX = []; testY = []
# 對數據進行混洗操作
random.shuffle(indexList)
# 切分訓練集和測試集
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)
print(wMat)
# 循環遍歷矩陣中的30組迴歸係數
for k in range(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] = ((yEst.T.A-array(testY))**2).sum()
# print(errorMat)
# 計算誤差估計值的均值
meanErrors = mean(errorMat,0)
minMean = float(min(meanErrors))
bestWeights = wMat[nonzero(meanErrors==minMean)]
# 不要使用標準化的數據,需要對數據進行還原來得到輸出結果
xMat = mat(xArr); yMat=mat(yArr).T
meanX = mean(xMat,0); varX = var(xMat,0)
unReg = bestWeights/varX
# 輸出構建的模型
print ("the best model from Ridge Regression is:\n",unReg)
print ("with constant term: ",-1*sum(multiply(meanX,unReg)) + mean(yMat))
# predict for lego's price
def regression5():
lgX = []
lgY = []
setDataCollect(lgX, lgY)
crossValidation(lgX, lgY, 10)
regression5()
測試算法:使用交叉驗證來測試不同的模型,分析哪個效果最好
使用算法:這次練習的目標就是生成數據模型