《Machine Learning in Action》—— 淺談線性回歸的那些事
tags:機器學習

《Machine Learning in Action》—— 淺談線性回歸的那些事
手撕機器學習演算法系列文章已經肝了不少,自我感覺品質都挺不錯的。目前已經更新了支援向量機SVM、決策樹、K-近鄰(KNN)、貝葉斯分類,讀者可根據以下內容自行「充電」(持續更新中):
- 《Machine Learning in Action》—— 剖析支援向量機,單手狂撕線性SVM: //www.zybuluo.com/tianxingjian/note/1755051
- 《Machine Learning in Action》—— 剖析支援向量機,優化SMO: //www.zybuluo.com/tianxingjian/note/1756832
- 《Machine Learning in Action》—— 懂的都懂,不懂的也能懂。非線性支援向量機: //www.zybuluo.com/tianxingjian/note/1758624
- 《Machine Learning in Action》—— Taoye給你講講決策樹到底是支什麼「鬼」: //www.zybuluo.com/tianxingjian/note/1757662
- 《Machine Learning in Action》—— 小朋友,快來玩啊,決策樹呦: //www.zybuluo.com/tianxingjian/note/1758236
- 《Machine Learning in Action》—— 女同學問Taoye,KNN應該怎麼玩才能通關: //www.zybuluo.com/tianxingjian/note/1759263
- 《Machine Learning in Action》—— 白話貝葉斯,「恰瓜群眾」應該恰好瓜還是恰壞瓜: //www.zybuluo.com/tianxingjian/note/1758540
- 《Machine Learning in Action》—— 淺談線性回歸的那些事: //www.zybuluo.com/tianxingjian/note/1761762
閱讀過上述那些文章的讀者應該都知道,上述內容都屬於分類演算法,分類的目標變數都是標稱型數據(關於標稱型和數據型數據的意思的可參考上文)。而本文主要講述的內容是線性回歸,它是一種回歸擬合問題,會對連續性數據做出預測,而非判別某個樣本屬於哪一類。
本文主要包括的內容有如下幾部分:
- 線性回歸,我們來談談小姐姐如何相親
- 揭開梯度下降演算法的神秘面紗
- 基於梯度下降演算法實現線性回歸擬合
一、線性回歸,我們來談談小姐姐如何相親
回歸預測,回歸預測,說到底就包括兩個部分。
一個是回歸(擬合),另一個是預測。回歸是為預測做準備或者說是鋪墊,只有基於已有的數據集我們才能構建一個的回歸模型,然後根據這個回歸模型來處理新樣本數據的過程就是預測。
而線性回歸就是我們的回歸模型屬於線性的,也就是說我們樣本的每個屬性特徵都最多是一次的(進來的讀者 應該都知道吧)
為了讓讀者對線性回歸有個基本的了解,我們來聊聊小姐姐的相親故事。
故事是這樣的。
在很久很久以前,有位小姐姐打算去相親,她比較在意對象的薪資情況,但這種事情也不太好意思直入主題,你說是吧?所以呢,她就想著通過相親對象本身的屬性特徵來達到一個預測薪資的目的。假如說這位小姐姐認為對象的薪資主要有兩個部分的數據的組成,分別是對象的年齡和頭髮量。對此,小姐姐想要構建出這麼一個關於薪資的線性模型:
中文形式的描述就是:
所以呢,小姐姐現在的目的就是想要得到這麼一個的值,然後觀察和詢問相親對象的發量以及年齡,就可以根據這個線性模型計算得出其相親對象的薪資情況。
那麼,如何得到的值呢???就在小姐姐腦闊疼的厲害之時,Taoye是這麼手握手教小姐姐的:「小姐姐,你可以先相親1000個對象,觀察並詢問對象的發量和年齡之後,然後通過社會工程學來得到他的薪資情況。有了這1000組對象數據之後,你就能訓練出
的值,從而得到誤差達到最小時候的這個線性模型」
小姐姐聽完Taoye的講述之後,真的是一語驚醒夢中人啊,心想:妙啊,就這麼干!!!
以上例子中的內容純屬Taoye胡扯,只為描述線性回歸的過程,不代表任何觀點。
二、揭開梯度下降演算法的神秘面紗
通過上述小姐姐的相親故事,相信各位看官都已經對線性回歸的過程有了一個基本的認識,而要具體操作線性回歸,我們還需明白一個在機器學習領域中比較重要的演算法,也就是梯度下降演算法。
要理解梯度下降演算法,我們可以將其類比成一個人下山的過程,這也是我們理解梯度下降演算法時候最常用的一個例子,也就是這麼一個場景:
有個人被困在山上,他現在要做的就是下山,也就是到達山的最低點。但是呢,現在整座山煙霧繚繞,可見度非常的低,所以下山的路徑暫時無法確定,他必須通過自己此刻所在地的一些資訊來一步步找到下山的路徑。此時,就是梯度下降演算法大顯身手的時候了。具體怎麼做呢?
是這樣的,首先會以他當前所在地為基準,尋找此刻所處位置的最陡峭的地方,然後朝著下降的方向按照自己的設定走一步。走一步之後,就來到了一個新的位置,然後將這個新的位置作為基準,再找到最陡峭的地方,沿著這個方向再走一步,如此循環往複,知道走到最低點。這就是梯度下降演算法的類別過程,上山同理也是一樣,只不過變成了梯度上升演算法。
梯度下降演算法的基本過程就類似於上述下山的場景。
首先,我們會有一個可微分的函數。這個函數就類似於上述的一座山。我們的目標就是找到這個函數的最小值,也就是上述中山的最低點。根據之前的場景假設,最快的下山的方式就是找到當前位置最陡峭的方向,然後沿著此方向向下走,在這個可微分函數中,梯度反方向就代表這此山最陡峭的方向,也就是函數下降最快的方向。因為梯度的方向就是函數變化最快的方向(在後面會詳細解釋)
所以,我們重複利用這個方法,在達到一個新的位置之後,反覆求取梯度,最後就能到達局部的最小值,這就類似於我們下山的過程。而求取梯度就確定了最陡峭的方向,也就是場景中測量方向的手段。那麼為什麼梯度的方向就是最陡峭的方向呢?接下來,我們從微分開始講起:
- 單變數
對於單變數微分來講,由於函數只有一個變數,所以此時的梯度就是函數的微分,所代表的意義就是在該點所對應的斜率。
- 多變數(以三個變數為例)
對於多變數函數來講,此時的梯度就不再是一個具體的值,而是一個向量。我們都知道,向量是有方向的,而該梯度的方向就代表著函數在指定點中上升最快的方向。
這也就說明了為什麼我們需要千方百計的求取梯度!我們需要到達山底,就需要在每一步觀測到此時最陡峭的地方,梯度就恰巧告訴了我們這個方向。梯度的方向是函數在給定點上升最快的方向,那麼梯度的反方向就是函數在給定點下降最快的方向,這正是我們所需要的。所以我們只要沿著梯度的方向一直走,就能走到局部的最低點!
現在,我們不妨通過程式碼來模擬實現這個過程。假如說,我們現在的目標函數是:
則其對對應的梯度為:
對此,我們可以通過如下程式碼來模擬梯度下降的過程,以尋找出到達最低點時候的值:

通過上述程式碼,我們可以發現,當函數值達到最低點的時候,此時我們的,與我們手動計算的
基本可以劃等號,這就是梯度下降所解決的問題。
針對上述程式碼,這裡我們主要說兩個點:
①:x_new = x_old - learning_rate * gradient(x_old)
在前進過程中更新x_new的時候,是通過x_old來進行的,之所以在梯度前加一個負號,主要是為了朝著梯度相反的方向前進。在前文我們也要提到,梯度的反方向就是函數在此點下降最快的方向。那麼如果是上坡,也就是梯度上升演算法,此時x_new的更新過程也就不需要加上負號了。
至於什麼時候是梯度上升,什麼時候是梯度下降,這個是根據我們實際情況是求最小值,還是最大值來決定的。
②:learning_rate
learning_rate
在梯度下降演算法中被稱作為學習率或者說是步長,意味著我們可以通過learning_rate
來控制每一步走的距離,其實就是不要走太快,從而錯過了最低點。同時也要保證不要走的太慢,導致我們打到最低點需要花費大量的時間,也就是效率太低了,需要迭代很多次才能滿足我們的需求。所以learning_rate
的選擇在梯度下降法中往往是很重要的!
需要合理的選擇learning_rate
值,一般來講可取0.01,具體問題還需具體分析。
總而言之,梯度下降演算法主要是根據函數的梯度來對x的值進行不斷的更新迭代,以求得函數到達最小值時候的x值。
當然了,以上是該演算法的一般形式,同時各位研究者也是提出了一些梯度下降演算法的變種形式,主要有以下三種
:
- 隨機梯度下降演算法(SGD,根據時間複雜度)
- 批量梯度下降演算法(BGD,根據數據量的大小)
- 小批量梯度下降演算法(MBGD,演算法準確性)
關於上述三種梯度下降演算法的變種形式,我們在這裡挖個坑,後面有機會再來慢慢把這個坑個填上。
三、基於梯度下降演算法實現線性回歸擬合
這裡程式碼實戰的話,其實是牽涉到對神經網路的理解,不過我們在這裡不著重講解神經網路的內容,只簡單的提一下,待手撕機器學習系列文章完成之後再來詳細看看。
參考資料:《TensorFlow深度學習》——龍龍老師
我們都知道,人的大腦中包含了大量的神經元細胞,每個神經元都通過樹突來獲取輸入的訊號,然後通過軸突傳遞並輸出訊號,而神經元與神經元之間相互連接從而構成了巨大的神經網路。生物神經元的結構如下圖所示:

1943年,心理學家沃倫·麥卡洛克(Warren McCulloch)和數理邏輯學家沃爾特·皮茨(Walter Pitts)通過對生物神經元的研究,提出了模擬生物神經元機制的人工神經網路的數學模型,這一成果被美國神經學家弗蘭克·羅森布拉特(Frank Rosenblatt)進一步發展成感知機(Perceptron)模型,這也是現代深度學習的基石。
我們從神經元的結構出發,來模擬這個神經元的訊號處理過程。
如下圖a所示,神經元輸入向量,經過函數映射:
後得到y,其中
為函數f的自身參數。在這裡,我們考慮一種簡化的情況,也就是線性變換:
,因為其中的w和x都是向量,所以我們將其展開為標量形式可表示為:
上述計算的邏輯過程可通過下圖b直觀展現

以上神經元含有多個輸入,為了方便理解,我們不妨進一步簡化模型,此時的n=1,即我們假設該線性模型為:
其中體現的是模型的斜率,而b體現的是截距,或者在這裡我們說是偏置。
我們知道,對於一條直線來講,我們只需要已知兩個點,求解二元一次方程組,就能得到該直線的具體表達式,也就是求解出的值。
理想狀態下的確是這樣的,但是現實總是殘酷的,我們所獲取到的數據可能存在一定的誤差,此時我們就根本無法構建出這麼一條完美的直線來切合這些數據點。我們不妨用來表示該觀測誤差,假設該誤差滿足
的高斯分布,則我們的模型可轉換為:
雖然我們不可能通過一條直線來完美的通過這些數據點,所以我們現在的需求就是儘可能找到這麼一條直線,使得所有數據點到這條直線的「距離」最小,那麼得到的這條直線就是我們比較滿意的模型。
那麼如何衡量所有數據點到達這條直線的「距離」最小?如何衡量這個模型的「好」與「不好」呢?這個時候就需要引出我們的損失函數了,一個很自然的想法就是求出當前模型的所有取樣點上的預測值與真實值之間差的平方和的均值來作為這個模型的損失函數,也就是我們常常所提到的均方誤差,損失函數表達如下:
當我們的損失函數計算的值比較大的時候,此時說明該直線的擬合效果並不好,當我們的損失函數計算的值比較小的時候,說明此時的擬合效果達到了一個不錯的程度。所以,我們不妨令損失函數值達到最小時,此時的模型參數為,則為:

讀到這裡,各位看官是不是知道下文如何走筆的了。沒錯,接下來就是通過梯度下降演算法來求解該損失函數的最小值,
對此,我們需要求解出損失函數分別對的偏導,求解過程如下:

即:
得到偏導之後,我們就可以根據舊的更新得到新的
,這就是一次更新迭代過程。更新之後,我們再次重新計算偏導並更新參數,如此不斷循環往複,知道我們計算的損失函數的值得到一個我們可接受的範圍,即達到了我們的目的。
下面,我們通過程式碼來模擬實現這個過程。
- NumPy隨機生成數據集,並通過Matplotlib初步觀察數據的分布
"""
Author: Taoye
微信公眾號: 玩世不恭的Coder
Explain: 用於生成樣本數據
Return:
x_data: 數據樣本的一個屬性
y_data: 數據樣本的另一個屬性
"""
def establish_data(data_number):
x_data = np.random.uniform(-10, 10, data_number)
eps = np.random.normal(0, 1, data_number)
y_data = x_data * 1.474 + 0.86 + eps
return x_data, y_data
if __name__ == "__main__":
x_data, y_data = establish_data(100)
from matplotlib import pyplot as plt
plt.scatter(x_data, y_data)
plt.show()
運行結果如下,可以看出數據分布大致可通過一條直線來進行擬合:

- 根據數據和當前的w、b值計算均方誤差
"""
Author: Taoye
微信公眾號: 玩世不恭的Coder
Explain: 計算均方誤差
Parameters:
x_data: 數據樣本的一個屬性
y_data: 數據樣本的另一個屬性
w_now: 當前的w參數
b_now: 當前的b參數
Return:
mse_value: 均方誤差值
"""
def calc_mse(x_data, y_data, w_now, b_now):
x_data, y_data = np.mat(x_data), np.mat(y_data)
_, data_number = x_data.shape
return np.power(w_now * x_data + b_now - y_data, 2).sum() / float(data_number)
- 單次對w、b參數進行更新迭代
"""
Author: Taoye
微信公眾號: 玩世不恭的Coder
Explain: 更新迭代一次w、b
Parameters:
x_data: 數據樣本的一個屬性
y_data: 數據樣本的另一個屬性
w_now: 當前的w參數
b_now: 當前的b參數
learning_rate: 學習率
Return:
w_new: 更新迭代之後的w
b_new: 更新迭代之後的b
"""
def step_gradient(x_data, y_data, w_now, b_now, learning_rate):
x_data, y_data = np.mat(x_data), np.mat(y_data)
w = (w_now * x_data + b_now - y_data) * x_data.T * 2 / x_data.shape[1]
b = (w_now * x_data + b_now - y_data).sum() * 2 / x_data.shape[1]
return w_now - w * learning_rate, b_now - b * learning_rate
- 多次迭代更新w、b(外循環)
"""
Author: Taoye
微信公眾號: 玩世不恭的Coder
Explain: 多次迭代更新w、b(外循環)
Parameters:
x_data: 數據樣本的一個屬性
y_data: 數據樣本的另一個屬性
starting_w: 初始的w參數
starting_b: 初試的b參數
learning_rate: 學習率
max_iter:最大迭代次數
Return:
w:得到的最終w
b: 得到的最終b
loss_list: 每次迭代計算的損失值
"""
def gradient_descent(x_data, y_data, starting_b, starting_w, learning_rate, max_iter):
b, w = starting_b, starting_w
loss_list = list()
for step in range(max_iter):
w, b = step_gradient(x_data, y_data, w, b, learning_rate)
loss = calc_mse(x_data, y_data, w, b)
loss_list.append(loss)
return w, b, np.array(loss_list)
- 擬合結果的可視化
"""
Author: Taoye
微信公眾號: 玩世不恭的Coder
Explain: 擬合結果的可視化
Parameters:
x_data: 數據樣本的一個屬性
y_data: 數據樣本的另一個屬性
w: 擬合得到的模型w參數
b: 擬合得到的模型b參數
loss_list: 每次更新迭代得到的損失函數的值
"""
def plot_result(x_data, y_data, w, b, loss_list):
from matplotlib import pyplot as plt
%matplotlib inline
plt.subplot(2, 1, 1)
plt.scatter(x_data, y_data)
x_line_data = np.linspace(-10, 10, 1000)
y_line_data = x_line_data * w + b
plt.plot(x_line_data, y_line_data, "--", color = "red")
plt.subplot(2, 1, 2)
plt.plot(np.arange(loss_list.shape[0]), loss_list)
plt.show()
程式運行結果如下:

從上方的運行結果來看,我們可以分析得到線性回歸模型的擬合效果還不錯,完全能夠體現出數據的分布規律。另外,通過損失函數的變化圖以及具體數值,我們可以觀察到,前期損失值的變化非常的大,到了後期基本居於平緩,看比如說第一次到後面計算的損失值分別為14.215、4.0139、1.941188…..,這就是梯度下降法所體現出來的效果,也就是說我們的損失函數值越大,我們梯度下降法優化的效果也就越明顯。
完整程式碼:
import numpy as np
"""
Author: Taoye
微信公眾號: 玩世不恭的Coder
Explain: 用於生成樣本數據
Return:
x_data: 數據樣本的一個屬性
y_data: 數據樣本的另一個屬性
"""
def establish_data(data_number):
x_data = np.random.uniform(-10, 10, data_number)
eps = np.random.normal(0, 1, data_number)
y_data = x_data * 1.474 + 0.86 + eps
return x_data, y_data
"""
Author: Taoye
微信公眾號: 玩世不恭的Coder
Explain: 計算均方誤差
Parameters:
x_data: 數據樣本的一個屬性
y_data: 數據樣本的另一個屬性
w_now: 當前的w參數
b_now: 當前的b參數
Return:
mse_value: 均方誤差值
"""
def calc_mse(x_data, y_data, w_now, b_now):
x_data, y_data = np.mat(x_data), np.mat(y_data)
_, data_number = x_data.shape
return np.power(w_now * x_data + b_now - y_data, 2).sum() / float(data_number)
"""
Author: Taoye
微信公眾號: 玩世不恭的Coder
Explain: 更新迭代一次w、b
Parameters:
x_data: 數據樣本的一個屬性
y_data: 數據樣本的另一個屬性
w_now: 當前的w參數
b_now: 當前的b參數
learning_rate: 學習率
Return:
w_new: 更新迭代之後的w
b_new: 更新迭代之後的b
"""
def step_gradient(x_data, y_data, w_now, b_now, learning_rate):
x_data, y_data = np.mat(x_data), np.mat(y_data)
w = (w_now * x_data + b_now - y_data) * x_data.T * 2 / x_data.shape[1]
b = (w_now * x_data + b_now - y_data).sum() * 2 / x_data.shape[1]
return w_now - w * learning_rate, b_now - b * learning_rate
"""
Author: Taoye
微信公眾號: 玩世不恭的Coder
Explain: 多次迭代更新w、b(外循環)
Parameters:
x_data: 數據樣本的一個屬性
y_data: 數據樣本的另一個屬性
starting_w: 初始的w參數
starting_b: 初試的b參數
learning_rate: 學習率
max_iter:最大迭代次數
Return:
w:得到的最終w
b: 得到的最終b
loss_list: 每次迭代計算的損失值
"""
def gradient_descent(x_data, y_data, starting_b, starting_w, learning_rate, max_iter):
b, w = starting_b, starting_w
loss_list = list()
for step in range(max_iter):
w, b = step_gradient(x_data, y_data, w, b, learning_rate)
loss = calc_mse(x_data, y_data, w, b)
loss_list.append(loss)
return w, b, np.array(loss_list)
"""
Author: Taoye
微信公眾號: 玩世不恭的Coder
Explain: 擬合結果的可視化
Parameters:
x_data: 數據樣本的一個屬性
y_data: 數據樣本的另一個屬性
w: 擬合得到的模型w參數
b: 擬合得到的模型b參數
loss_list: 每次更新迭代得到的損失函數的值
"""
def plot_result(x_data, y_data, w, b, loss_list):
from matplotlib import pyplot as plt
%matplotlib inline
plt.subplot(2, 1, 1)
plt.scatter(x_data, y_data)
x_line_data = np.linspace(-10, 10, 1000)
y_line_data = x_line_data * w + b
plt.plot(x_line_data, y_line_data, "--", color = "red")
plt.subplot(2, 1, 2)
plt.plot(np.arange(loss_list.shape[0]), loss_list)
plt.show()
if __name__ == "__main__":
x_data, y_data = establish_data(100)
w, b, loss_list = gradient_descent(x_data, y_data, 0, 0, 0.01, 1000)
plot_result(x_data, y_data, w[0, 0], b, loss_list)
以上就是本文線性回歸的全部內容了,總體上來講還是挺簡單的,難度係數也沒那麼大,更多關於線性回歸的內容,我們後面再來講解。
這裡我們對線性回歸做一個簡單的總結:
優點:結果比較容易理解,計算上並不複雜,沒有太多複雜的公式和花里胡哨的內容
缺點:對非線性的數據擬合不好,時間複雜度還有一定的優化空間
適用數據類型:數值型和標稱型數據
我是Taoye,愛專研,愛分享,熱衷於各種技術,學習之餘喜歡下象棋、聽音樂、聊動漫,希望藉此一畝三分地記錄自己的成長過程以及生活點滴,也希望能結實更多志同道合的圈內朋友,更多內容歡迎來訪微信公主號:玩世不恭的Coder。
我們下期再見,拜拜~~~
參考資料:
[1] 《機器學習實戰》:Peter Harrington 人民郵電出版社
[2] 《TensorFlow深度學習》:龍龍老師
[3]梯度下降演算法原理講解://blog.csdn.net/qq_41800366/article/details/86583789
推薦閱讀
《Machine Learning in Action》—— 白話貝葉斯,「恰瓜群眾」應該恰好瓜還是恰壞瓜
《Machine Learning in Action》—— 女同學問Taoye,KNN應該怎麼玩才能通關
《Machine Learning in Action》—— 懂的都懂,不懂的也能懂。非線性支援向量機
《Machine Learning in Action》—— hao朋友,快來玩啊,決策樹呦
《Machine Learning in Action》—— Taoye給你講講決策樹到底是支什麼「鬼」
《Machine Learning in Action》—— 剖析支援向量機,優化SMO
《Machine Learning in Action》—— 剖析支援向量機,單手狂撕線性SVM
print( “Hello,NumPy!” )
幹啥啥不行,吃飯第一名
Taoye滲透到一家黑平台總部,背後的真相細思極恐
《大話資料庫》-SQL語句執行時,底層究竟做了什麼小動作?