深度學習筆記—線性迴歸(基礎)

線性迴歸

在深度學習的基礎學習階段總是逃不過幾個經典的實戰問題,其中之一就是線性迴歸的經典數據集:Boston房價預測。
要解決這個問題我們需要用到一種很常見的機器學習算法:線性迴歸,使用線性迴歸解決問題之前,我們要搞清楚什麼是線性迴歸,線性迴歸有什麼用,怎麼用線性迴歸去解決Boston房價預測問題。

1. 什麼是線性迴歸

讓我們看一看數學上對迴歸的定義:迴歸,指研究一組隨機變量(Y1 ,Y2 ,…,Yi)和另一組(X1,X2,…,Xk)變量之間關係的統計分析方法,又稱多重回歸分析。通常Y1,Y2,…,Yi是因變量,X1、X2,…,Xk是自變量。(摘自百度百科)
簡單來說迴歸是指一組隨機因變量和另一組隨機自變量之間的關係,這個定義很像函數中的定義,但明顯其範圍要廣於函數,數學上將回歸分爲四類,分別是: 線性迴歸、曲線迴歸、二元logistic迴歸、多元logistic迴歸。
在Boston房價預測問題中,我們要使用的就是線性迴歸,所以什麼是線性迴歸?線性迴歸是利用數理統計中迴歸分析,來確定兩種或兩種以上變量間相互依賴的定量關係的一種統計分析方法,運用十分廣泛。其表達形式爲y = w’x+e,e爲誤差服從均值爲0的正態分佈。看到這個表達式我們就很熟悉了,這個表達形式很像數學中的一元函數表達式,如果已知函數表達式,根據表達式我們可以求出任意x位置的y值,換個說法就是,我們可以預測出任意x位置的y值,到這裏我們就可以明白如何利用線性迴歸進行預測了,我們需要求出線性迴歸方程,根據線性迴歸方程進行預測。

2. 如何理解線性迴歸

Boston房價分析的數據集很複雜有CRIM, ZN ,INDUS ,CHAS,NOX,RM,AGE,DIS,RAD,TAX,PTRATIO,LSTAT,MEDV等多個自變量,讓人看的無從下手,所以我們先從一個簡單的數據集看起,該數據集值規格爲100 x 2 100行兩列,第一列爲自變量,第二列爲因變量數據如圖所示:(文末可下載該數據集)在這裏插入圖片描述
單獨看數據我們看不出來什麼,但是我們可以將數據放在平面直角座標系中,可以更直觀的看出數據之間的關係,代碼如下

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
#reviews = pd.read_csv('./data/data.csv')
reviews = np.genfromtxt("./data/data.csv", delimiter=",")#delimiter 分割符號
fig, ax = plt.subplots()
ax.scatter(reviews[:,0], reviews[:,1])
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.show()

根據代碼可以做出散點圖:
在這裏插入圖片描述
通過圖像我們可以直觀的看到數據雖然不規律,但是在某種程度上在集中於兩條直線之間,根據線性迴歸定義,我們需要找出來一條線,最大程度的經過這些點,如下圖所示:
在這裏插入圖片描述
除去上下兩側邊緣的直線外,其他的直線的擬合效果好像都不錯,但是究竟什麼直線的擬合效果最好呢,我們這裏就需要引入一個度量擬合度好不好的概念:損失函數

什麼是損失函數

簡單來說損失函數就是衡量模型輸出和真實標籤之間的差距,模型就是我們上圖畫出的諸多直線 ,一條直線就叫一個模型,同一個x值在不同模型中對應不同y值,而標籤就是給定數據集中與因變量對應的自變量的值,我們稱之爲標籤集。

怎麼計算損失

我們知道了用什麼度量模型的擬合效果,但是我們該怎麼利用損失函數這個工具呢,在上圖中,我們將直線和數據放在同一個直角座標系中,所有的數據點要麼是在直線上,要麼是在直線外,那麼我們就可以計算所有的點到直線的距離和,使這個距離和越小越好,越小說明落在直線上的點越多,模型越精確,預測值自然就接近真實值。所以我們可以推出如下損失計算公式:
在這裏插入圖片描述
其中Y是標籤,而f(x)則是根據模型計算輸出的預測值這樣看不是很直接,我們將公式展開如下

根據公式我們可以看出,在線性迴歸的損失計算中,就是求出每個模型給出的輸出和標籤的差距。所以我們的目標函數很清晰,就是讓loss儘可能的小即預測值無限逼近真實值,我們觀察上圖的公式,loss函數只與w參數和b參數有關,所以我們只需要關注w和b,使loss值儘可能的小,so,我們該怎麼求解合適的w和b參數呢,在模型的建立過程中,w和b不可能一步到位,我們只能利用Gradient Descent(梯度下降)一步一步的去更新w和b參數,使損失降到最小。

Gradient Descent

梯度下降法,簡單來說就是根據梯度找函數的最低點,梯度是一個由所有的軸方向的偏微分組成的向量,大小是由各個軸的方向的偏微分大小綜合決定,方向是指向數值增長最快的方向,即函數值增大的方向,如果將梯度方向翻轉,很顯然會指向函數值減小的方向,而我們要找的就是函數的最低點,我們很顯然不可能一步到位,直接到達函數的最低點,所以我們可以隨機的賦予w和b參數初值,再利用梯度下降,使其“慢慢的”到達函數最低點,如下圖所示(圖片引用自網絡):
綠色線爲損失函數圖像
在上圖中我們可以看出,w變化使損失函數慢慢到達最低點,如果我們將梯度下降類比成下山,我們該怎麼才能到達山腳。
第一步,明確自己現在所處的位置

第二步,找到相對於該位置而言下降最快的方向

第三步, 沿着第二步找到的方向走一小步,到達一個新的位置,此時的位置肯定比原來低

第四部, 回到第一步

第五步,終止於最低點

按照以上5步,最終達到最低點,這就是梯度下降的完整流程。
瞭解了流程我們就需要明確,怎麼走出下一步,梯度給了我們下一步的方向,下一個w和b的值是什麼,也就是下一步的位置在哪?換句話說我們該怎麼更新w和b,這裏我們引入一個新的參數步長η,也叫學習率,首先我們來看參數更新公式:
在這裏插入圖片描述
在這裏插入圖片描述
即新的w值等於舊值與步長和損失關於w參數的偏導的積的差,b的參數更新公式與w的參數更新公式如出一轍,我們來看一下參數更新公式,通俗的理解就是,我們步長固定,但是在下山的時候如果我們所在的位置山越陡,我們走一步下降的高度差越大,並且我們的方向總沿着梯度下降的方向前進,那是不是步長越大越好呢,很明顯不是的,我們來用一張圖形象的看一下步長過大會出現什麼情況(圖片引用自網絡):
在這裏插入圖片描述
我們可以很清楚的看到下一步的落點直接越過了函數的最低點,當越過最低點後重新計算梯度,發現梯度方向與前一個的相反,所以更新參數,但是步長過大,又一次越過函數最低點,這樣重複更新會導致震盪,如果運氣好震盪多次會達到最低點,如果運氣不會則會造成參數持續更新但始終無法到達最低點。(圖片引用自網絡)在這裏插入圖片描述
那麼如果我們取一個較小的步長會不會更好一些呢?我們直接來看圖(圖片引用自網絡)
在這裏插入圖片描述
由圖我們可以看出,參數會持續更新,但是步長過小,導致需要更新很多次才能達到函數最低點,就像下山的時候走着小碎步,方向是正確的,但是要花費大量的時間,這明顯嚴重的浪費了計算機的計算資源。所以我們需要選擇一個合適的步長,使其兼顧效率也不至於造成震盪。(圖片引用自網絡)
在這裏插入圖片描述
我們目前只說了梯度下降最簡單的情況,即只有一個參數更新,我們可以先看看有多個參數時的動態圖,以便對於梯度下降有更直觀的理解。(圖片引用自網絡)在這裏插入圖片描述在這裏插入圖片描述
圖中不同顏色的線表示不同的優化算法,雖然速度不同,路徑不同,但無一例外的都到達了函數的最低點。
OK,到這裏我們有了對線性迴歸的基礎理解,我們來解決上文提出來的只有一個因變量的預測問題。

3.使用線性迴歸解決簡單問題

在上文提到的簡單數據集中,我們可以看到只有因變量自變量兩列數據,所以我們可以使用簡單線性迴歸y = wx + b,根據線性迴歸求解問題的步驟和公式,我們可以將該程序分成,主函數,梯度計算函數,參數更新函數,損失計算函數四個代碼塊。

  • 損失計算函數

根據損失函數的計算公式我們可以寫出如下python代碼:
在這裏插入圖片描述

#損失計算函數
def compute_error_for_line_given_points(b, w, points):
    totalError = 0
    for i in range(0, len(points)):
        x = points[i, 0]
        y = points[i, 1]
        # computer mean-squared-error
        totalError += (y - (w * x + b)) ** 2#所有數據的損失和
    # average loss for each point
    return totalError / float(len(points))

代碼的核心就是循環求出所有的數據的平均損失

  • 梯度計算和參數更新

因爲該例子只有一個自變量一個因變量,我們只需要求損失函數關於w和b的偏導,代碼如下:

#梯度計算函數
def step_gradient(b_current, w_current, points, learningRate):
    b_gradient = 0
    w_gradient = 0
    N = float(len(points))
    for i in range(0, len(points)):
        x = points[i, 0]
        y = points[i, 1]
        # grad_b = 2(wx+b-y)
        b_gradient += (2/N) * ((w_current * x + b_current) - y)
        # grad_w = 2(wx+b-y)*x
        w_gradient += (2/N) * x * ((w_current * x + b_current) - y)
    # update w'
    new_b = b_current - (learningRate * b_gradient)
    new_w = w_current - (learningRate * w_gradient)
    return [new_b, new_w]

梯度計算函數的核心爲根據偏導公式grad_b = 2(wx+b-y)和grad_w = 2(wx+b-y)*x迭代求解每一個自變量對應的梯度,並且求和。
有了梯度計算有了損失計算,剩下的就是根據損失和梯度更新參數,使其逼進函數最低點。

  • 開始訓練

根據梯度下降法的解決問題的流程,和規定的迭代次數進行參數計算

def gradient_descent_runner(points, starting_b, starting_w, learning_rate, num_iterations):
    b = starting_b
    w = starting_w
    # update for several times
    for i in range(num_iterations):#控制迭代次數
        b, w = step_gradient(b, w, np.array(points), learning_rate)
    return [b, w]
  • 完整代碼
import numpy as np
# y = wx + b
#損失計算函數
def compute_error_for_line_given_points(b, w, points):
    totalError = 0
    for i in range(0, len(points)):
        x = points[i, 0]
        y = points[i, 1]
        # computer mean-squared-error
        totalError += (y - (w * x + b)) ** 2#所有數據的損失和
    # average loss for each point
    return totalError / float(len(points))
#梯度計算函數
def step_gradient(b_current, w_current, points, learningRate):
    b_gradient = 0
    w_gradient = 0
    N = float(len(points))
    for i in range(0, len(points)):
        x = points[i, 0]
        y = points[i, 1]
        # grad_b = 2(wx+b-y)
        b_gradient += (2/N) * ((w_current * x + b_current) - y)
        # grad_w = 2(wx+b-y)*x
        w_gradient += (2/N) * x * ((w_current * x + b_current) - y)
    # update w'
    new_b = b_current - (learningRate * b_gradient)
    new_w = w_current - (learningRate * w_gradient)
    return [new_b, new_w]
#更新參數
def gradient_descent_runner(points, starting_b, starting_w, learning_rate, num_iterations):
    b = starting_b
    w = starting_w
    # update for several times
    for i in range(num_iterations):#控制迭代次數
        b, w = step_gradient(b, w, np.array(points), learning_rate)
    return [b, w]
#開始訓練
def run():
	
    points = np.genfromtxt("./data/data.csv", delimiter=",")#delimiter 分割符號
    learning_rate = 0.0001#學習率
    initial_b = 0 # initial y-intercept guess
    initial_w = 0 # initial slope guess
    num_iterations = 1000#迭代次數
    print("Starting gradient descent at b = {0}, w = {1}, error = {2}"
          .format(initial_b, initial_w,
                  compute_error_for_line_given_points(initial_b, initial_w, points))
          )#剛開始的w和b參數 以及初始損失
    print("Running...")
    [b, w] = gradient_descent_runner(points, initial_b, initial_w, learning_rate, num_iterations)
    print("After {0} iterations b = {1}, w = {2}, error = {3}".
          format(num_iterations, b, w,
                 compute_error_for_line_given_points(b, w, points))
          )

if __name__ == '__main__':
    run()

運行結果如下
在這裏插入圖片描述
經過線性迴歸後,我們成功的計算出了w和b參數的值,並且將損失值由五千多降至一百多,由此可見,線性迴歸可以解決預測問題,Boston房價問題因爲參數更多更復雜,我們放在下一節進行討論。
代碼所需數據集下載:鏈接:https://pan.baidu.com/s/1yc2booiYkC_EzNiBq_pFdw
提取碼:f9dv

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章