2020-08-19--梯度下降法01

  • 梯度下降法簡(jiǎn)介
  • 多元線性回歸中的梯度下降法
  • 隨機(jī)梯度下降法
  • 梯度下降法 的調(diào)試

1.梯度下降法簡(jiǎn)介

  • 不是一個(gè)機(jī)器學(xué)習(xí)算法
  • 是一種基于搜索的最優(yōu)化方法
  • 作用:最小化損失函數(shù) (最小值的點(diǎn))
  • 梯度上升法:最大化一個(gè)效用函數(shù)(最大值的點(diǎn) )

1.1一元線性回歸梯度下降原理分析

首先我們一元線性回歸為例:

一元線性回歸的目標(biāo):

我們的目標(biāo)是求a和b的值先朦,以作到損失函數(shù)最小耗绿。

在上式中X(i)和y(i)為訓(xùn)練集數(shù)據(jù)目胡,我們假設(shè)這個(gè)直線最終結(jié)果是一個(gè)沒(méi)有截距過(guò)原點(diǎn)的直線,也就是將b看作一個(gè)常數(shù)(或者將b直接去掉)亡问,對(duì)公式進(jìn)行變換:

對(duì)于第i個(gè)數(shù)據(jù)的損失函數(shù)表達(dá)式為:

那么這樣看的話這是一個(gè)關(guān)于a的一元二次方程。
總共有m個(gè)數(shù)據(jù),他們每一個(gè)損失函數(shù)的值相加列粪,最后相加的結(jié)果還是一個(gè)一元二次方程福侈。

所以我們的目標(biāo)就是找到該方程的最小值酒来。

參數(shù)theta對(duì)應(yīng)的損失函數(shù)J的值對(duì)應(yīng)的示例圖,我們需要找到使得損失函數(shù)值J取得最小值對(duì)應(yīng)的theta(這里是二維平面肪凛,也就是我們的參數(shù)只有一個(gè))

1.我們要找的就是圖中的theta(m)值役首。
  • 在直線方程中,導(dǎo)數(shù)代表斜率显拜。
  • 在曲線方程中衡奥,導(dǎo)數(shù)代表切線斜率。斜率越大远荠,變化越快矮固。
  • theta越靠近theta(m),J的變化越慢
2.怎么理解圖中的導(dǎo)數(shù)可以代表方向,對(duì)應(yīng)J增大的方向以及公式譬淳?

我們對(duì)圖中任意一點(diǎn)求導(dǎo)(切線)档址,這個(gè)切線都是有方向的

  • 在最小值左側(cè),切線的斜率是負(fù)的邻梆,對(duì)應(yīng)途中的(1)守伸,它的指向?yàn)镴增大的方向
  • 在最小值的右側(cè),切線斜率是正的浦妄,對(duì)應(yīng)圖中的(2)尼摹,它的指向也為J增大的方向
  • 而我們的目標(biāo)是向最小值的為晃著靠近,所以對(duì)每一個(gè)點(diǎn)求導(dǎo)之后剂娄,取相反數(shù)蠢涝,那么這樣切線就指向J減小的方向。
3.對(duì)于學(xué)習(xí)率n阅懦,接下來(lái)介紹:

η太小和二,會(huì)減慢收斂學(xué)習(xí)速度:

η太大,甚至導(dǎo)致不收斂:

那么這樣的話表達(dá)式:

我們?cè)谥老逻叺脑砭涂梢岳斫饬恕?/p>

尋找最小點(diǎn)theta(m)的原理過(guò)程:

在圖中的線上隨機(jī)尋找一個(gè)點(diǎn)耳胎,我們要不斷的更新theta的值惯吕,而這個(gè)更新值與原值之間的區(qū)間就是上邊的公式(我們暫且將公式值即為p)惕它,那么下一次更新的值就為(theta+p)。由于這個(gè)更新值一定指向J縮小的方向废登,所以theta不斷靠近theta(m)怠缸,直到某一次theta對(duì)應(yīng)的J比上一次的theta值對(duì)應(yīng)的J小于某一個(gè)很小的值(越靠近theta(m),J的變化速度越小),說(shuō)明我們找到了該點(diǎn)theta钳宪。

4.其他注意事項(xiàng)

并不是所有函數(shù)都有唯一的極值點(diǎn)

5.解決方案:
  • 多次運(yùn)行揭北,隨機(jī)化初始點(diǎn)
  • 梯度下降法的初始點(diǎn)也是一個(gè)超參數(shù)
6.有b時(shí)的情況

在之前的分析中我們把b看作一個(gè)常數(shù)/0。

如果加上b的話吏颖,那么損失函數(shù)就隨著a搔体,b兩個(gè)參數(shù)而做改變,這就是一個(gè)三維的空間半醉,如果做圖的話疚俱,x為theta,y為b缩多,z為J:

那么畫(huà)出來(lái)就類(lèi)似于倒過(guò)來(lái)的大鐘呆奕。

1.2模擬梯度下降法

接下來(lái)我們使用代碼來(lái)實(shí)現(xiàn)上邊的分析過(guò)程。

1.創(chuàng)建數(shù)據(jù)集衬吆,畫(huà)圖:
import numpy as np
import matplotlib.pyplot as plt

# 生成等差數(shù)列[-1,6],個(gè)數(shù)為141個(gè)
plot_x = np.linspace(-1,6,141)
print(plot_x)
# print(len(plot_x))

# 生成一元二次方程
plot_y = (plot_x-2.5)**2 - 1
print(plot_y)

# 畫(huà)圖
plt.plot(plot_x,plot_y)
plt.show()

運(yùn)行:

2.實(shí)現(xiàn)梯度下降:
# 學(xué)習(xí)率n
eta = 0.1
# 最小差距
epsilon = 1e-8

# 定義生成J值得函數(shù)
def J(theta):
    return (theta-2.5)**2 - 1

# 定義d(J)/d(theta)得值
def DJ(thate):
    return 2*(thate-2.5)

# 設(shè)置初始theta
theta = 0.0
while True:
    # 記錄當(dāng)前位置,后邊用于判斷是否跳出
    last = theta

    # 計(jì)算下一個(gè)點(diǎn)的位置,直接賦給循環(huán)變量theta
    theta = theta-eta * DJ(theta)

    if abs(J(theta) - J(last)) < epsilon:
        break

print(theta)
print(J(theta))
# 2.499891109642585
# -0.99999998814289

分析:

  1. 首先定義超參數(shù)n和最小差距梁钾,然后定義兩個(gè)函數(shù)J和DJ,計(jì)算某一點(diǎn)的J值逊抡,以及計(jì)算某一點(diǎn)J對(duì)theta的導(dǎo)姆泻。
  2. 設(shè)置超參數(shù)--初始點(diǎn)的theta,進(jìn)入無(wú)限迭代中冒嫡,先記錄本次點(diǎn)的theta值定義last拇勃,然后計(jì)算下一個(gè)點(diǎn)的theta,判斷本次theta和上一次theta點(diǎn)的J值差距是否小于最小差距孝凌,若是則跳出方咆,否則繼續(xù)循環(huán)。

3.生成梯度下降尋找最小值的過(guò)程展示:

import matplotlib.pyplot as plt
import numpy as np

# 設(shè)置初始theta
theta = 0.0
# 記錄尋找過(guò)程中的點(diǎn)
theta_list = []
while True:
    # 記錄當(dāng)前位置,后邊用于判斷是否跳出
    last = theta
    theta_list.append(theta)
    # 計(jì)算下一個(gè)點(diǎn)的位置,直接賦給循環(huán)變量theta
    theta = theta-eta * DJ(theta)

    if abs(J(theta) - J(last)) < epsilon:
        break
print(len(theta_list))      # 45

# 畫(huà)圖
# J函數(shù)的x,y
plot_x = np.linspace(-1,6,141)
plot_y = J(plot_x)

plt.plot(plot_x,plot_y)
plt.plot(theta_list,J(np.array(theta_list)),color='r',marker='+')
plt.show()

運(yùn)行:

由圖:
從左到右的過(guò)程中蟀架,+號(hào)越來(lái)月稀疏瓣赂。
總共循環(huán)了45次,找到了這個(gè)theta最小值辜窑。
這個(gè)循環(huán)次數(shù)是由學(xué)習(xí)率n钩述,初始點(diǎn),和最小差距決定的穆碎。
例如:
1.將學(xué)習(xí)率調(diào)小

將學(xué)習(xí)率n為0.000001
那么,循環(huán)次數(shù)達(dá)到了1956011次

運(yùn)行:

我們可以多次改變初始點(diǎn)的位置以保證準(zhǔn)確性职恳。

2.調(diào)大學(xué)習(xí)率

將學(xué)習(xí)率n為0.8
那么所禀,循環(huán)次數(shù)為21次

運(yùn)行:

學(xué)習(xí)率不能調(diào)的過(guò)大方面,結(jié)果會(huì)過(guò)大,會(huì)報(bào)錯(cuò)色徘。

修改J函數(shù):

def J(theta):
    try:
        return (theta-2.5)**2 - 1
    except:
        return float('inf')       # 返回一個(gè)很大的float數(shù)

J()在返回值進(jìn)入下方循環(huán)中時(shí)恭金,能保證不報(bào)錯(cuò),但是會(huì)陷入死循環(huán)中褂策,因?yàn)樵趯W(xué)習(xí)率很大時(shí)横腿,在x軸上的跳躍很快,當(dāng)循環(huán)次數(shù)達(dá)到一定程度斤寂,就會(huì)超出范圍耿焊。

所以在下邊我們封裝為函數(shù)后,可以加一個(gè)循環(huán)次數(shù)的參數(shù)遍搞,就可以查看其超出范圍是怎么回事了罗侯。

3.封裝為函數(shù)
def gradient_descent(init_theta,eta,n_iters=1e4,epsilon=1e-8):
    '''

    :param init_theta: 初始點(diǎn)的theta值
    :param eta:  學(xué)習(xí)率
    :param n_iters: 循環(huán)次數(shù),默認(rèn)為1e4
    :param epsilon:  最小差距值,默認(rèn)為1e-8
    :return:
    '''
    # 將初始化的init_theta賦給theta
    theta = init_theta
    i_iter = 0       # 初始化循環(huán)次數(shù)
    theta_history = []      #  初始化路徑列表

    while i_iter < n_iters:        # 當(dāng)循環(huán)次數(shù)小于參數(shù)n_iter時(shí),執(zhí)行循環(huán)
        last = theta             # 記錄本次的theta
        theta_history.append(theta)          # 傳進(jìn)列表
        theta = theta - eta * DJ(theta)        # 計(jì)算下次的theta
        # 判斷上次的J值和本次的J值差距是否小于最小值
        if abs(J(theta) - J(last)) < epsilon:
            break
        # 循環(huán)次數(shù)+1 
        i_iter += 1
    return theta,theta_history      # 最佳theta值和尋找theta值路徑列表

調(diào)用:

# 調(diào)用函數(shù)
eta = 1.1
result,result_list = gradient_descent(init_theta=0.0,eta=eta,n_iters=10)
print(result)
print(result_list)

# 畫(huà)圖
plt.plot(plot_x,plot_y)
plt.plot(result_list,J(np.array(result_list)),color='r')
plt.show()

將eta設(shè)置為1.1后,循環(huán)次數(shù)設(shè)為9次溪猿,運(yùn)行:

開(kāi)始值很小钩杰,慢慢的J值變化越來(lái)越快,就會(huì)溢出诊县。

2. 線性回歸中的梯度下降法

上述的過(guò)程實(shí)現(xiàn)了簡(jiǎn)單的線性回歸關(guān)于y = ax的實(shí)現(xiàn)讲弄,而y = ax+b以及多元線性回歸中的y = a(0)+a(1)x(1)+a(2)x(2)+...a(n)x(n)并沒(méi)有實(shí)現(xiàn)。

來(lái)看看多元線性回歸:

y軸還是J函數(shù)依痊,x軸變?yōu)橐粋€(gè)數(shù)據(jù)集---(theta(0),theta(1),...theta(n))垂睬。為了表達(dá)清除,化成平面圖抗悍,其實(shí)這個(gè)模型圖是一個(gè)n維的模型圖驹饺,我們畫(huà)不出來(lái),但是我們可以畫(huà)一個(gè)三維的來(lái)看一下:

一個(gè)三維空間中的梯度下降法(x,y為系數(shù)缴渊,z為損失函數(shù)):

在這里中theta是一個(gè)向量赏壹,可將y = ax+b中的b看作a(0),那么對(duì)于這個(gè)一元線性回歸來(lái)說(shuō),這個(gè)theta就是一個(gè)有兩行一列的向量衔沼。多元線性方程是多行一列的向量蝌借。,其實(shí)本質(zhì)上都是一樣的指蚁。

原理:
原理與一元線性回歸一樣菩佑,隨即設(shè)置一個(gè)點(diǎn),不斷的迭代更新該點(diǎn)凝化,進(jìn)而找出最優(yōu)點(diǎn)稍坯。

  • 這里就牽扯到一個(gè)變量--更新量。也就是一元中的【-n(d(J)/d(theta))】
    在多元中,由于theta是一個(gè)向量瞧哟,那么要不斷更新該點(diǎn)的位置混巧,那么這個(gè)更新量也必須是一個(gè)向量,該向量用【-n(J對(duì)每一個(gè)theta的偏導(dǎo))】勤揩。
  • 這樣(廣播操作)向量-向量后的結(jié)果還是向量咧党,達(dá)到了移動(dòng)該點(diǎn)的效果。對(duì)應(yīng)圖中的(1)(2)點(diǎn)的效果陨亡。
  • 根據(jù)新的theta求出該點(diǎn)的J,比較差距跳出即可傍衡。

1.損失函數(shù)求導(dǎo)的公式推導(dǎo)

對(duì)于多元的損失函數(shù)來(lái)說(shuō),他不想一元的J那樣求導(dǎo)的話一眼能看出來(lái)负蠕,
例如:
J =(theta-2.5)2 -1
DJ = 2
(theta-2.5)
多元的J比較復(fù)雜:

不可能看出來(lái)答案蛙埂,所以我們要對(duì)它進(jìn)行推導(dǎo)。

推導(dǎo):

推導(dǎo)思路與線性回歸章節(jié)中的思想一樣虐急,將y_head表達(dá)為兩個(gè)矩陣的點(diǎn)乘進(jìn)行計(jì)算:

y_head = X(b) (*) theta

X(b)和theta的表示:

所以對(duì)J可以表示為:

對(duì)于J求導(dǎo):

求導(dǎo)的依據(jù)【標(biāo)量對(duì)列向量】的求導(dǎo)方法進(jìn)行求導(dǎo)箱残。

但是上面推導(dǎo)出的式子的大小是和樣本數(shù)有關(guān)的,m越大止吁,結(jié)果越大被辑,這是不合理的,我們希望和m無(wú)關(guān)敬惦,所以對(duì)損失函數(shù)J除以m:

那么J導(dǎo)就變?yōu)椋?/p>

這樣的話J函數(shù)對(duì)theta向量求導(dǎo)后的數(shù)據(jù)就表示出來(lái)了盼理。

2.在線性回歸的梯度下降代碼實(shí)現(xiàn)

1.J的函數(shù)定義

損失函數(shù)的公式:


根據(jù)公式得知參數(shù)有 測(cè)試數(shù)據(jù):X_b,對(duì)應(yīng)真實(shí)值y,以及theta值俄删。

# 損失函數(shù)J
def J(theta,X_b,y):
    try:
        return np.sum((y - X_b.dot(theta))**2)/len(X_b)
    except:
        return float('inf')
2.DJ的實(shí)現(xiàn)

對(duì)DJ的公式進(jìn)行分析:

DJ的公式結(jié)果是一個(gè)向量宏怔,其中數(shù)據(jù)的個(gè)數(shù)是由theta向量個(gè)數(shù)決定的,因?yàn)樗恳豁?xiàng)都是J/theta的導(dǎo)畴椰。所以y = ax+b其實(shí)就相當(dāng)于只有theta(0)和theta(1)兩個(gè)元素臊诊。

我們將它分為兩個(gè)部分,第一部分是對(duì)theta(0)的導(dǎo)斜脂,其余部分是第二部分抓艳。這樣我們便于計(jì)算。

參數(shù)列表:測(cè)試集數(shù)據(jù)X_b,對(duì)應(yīng)真實(shí)值y帚戳,以及theta向量玷或。

DJ函數(shù):

def DJ(theta,X_b,y):
    res = np.empty(len(theta))
    # 第一個(gè)元素
    res[0] = np.sum(X_b.dot(theta) - y)
    # 第二個(gè)往后
    for i in range(1,len(theta)):
        res[i] = (X_b.dot(theta) - y).dot(X_b[:,i])

    return res *2 / len(X_b)

分析:

  1. 首先創(chuàng)建一個(gè)與theta向量等長(zhǎng)的數(shù)組res,用于填充數(shù)據(jù)片任。這個(gè)原因上邊說(shuō)過(guò)偏友。計(jì)算第一部分?jǐn)?shù)據(jù)的值--計(jì)算括號(hào)內(nèi)數(shù)據(jù)直接求和即可,賦給res[0]对供。
  2. 其他數(shù)據(jù)項(xiàng)使用for循環(huán)循環(huán)計(jì)算位他,為了便于計(jì)算,我們把它換位向量的點(diǎn)乘。
    首先分析這其中一項(xiàng):
  • 該數(shù)據(jù)項(xiàng)是m個(gè)數(shù)據(jù)值之和棱诱,他的每一項(xiàng)數(shù)據(jù)值都是標(biāo)量*標(biāo)量泼橘,例如第二行分析:


所以就可以將公式看作(以第二行數(shù)據(jù)為例):

那么用就可以在代碼用for循環(huán)給每一項(xiàng)賦值涝动。

  1. 最后返回res * 2 / len(X_b)迈勋。
3.梯度下降函數(shù)

與之前的y = ax的函數(shù)大概一致,只是在函數(shù)中調(diào)用J和DJ函數(shù)時(shí)要使用的參數(shù)X_b和y要加在該函數(shù)的參數(shù)中醋粟。

gradient_descent_many:

def gradient_descent_many(X_b,y,init_theta,eta,n_iters=1e4,epsilon=1e-8):
    '''
    :param X_b: 數(shù)據(jù)集靡菇,經(jīng)過(guò)改造(第0列都為1)
    :param y:  數(shù)據(jù)集對(duì)應(yīng)正確值
    :param init_theta: 初始點(diǎn)的theta值,一個(gè)向量
    :param eta:  學(xué)習(xí)率
    :param n_iters: 循環(huán)次數(shù),默認(rèn)為1e4
    :param epsilon:  最小差距值,默認(rèn)為1e-8
    :return: theta向量
    '''
    # 將初始化的init_theta賦給theta
    theta = init_theta
    i_iter = 0       # 初始化循環(huán)次數(shù)
    # theta_history = []      #  初始化路徑列表

    while i_iter < n_iters:        # 當(dāng)循環(huán)次數(shù)小于參數(shù)n_iter時(shí),執(zhí)行循環(huán)
        last = theta             # 記錄本次的theta
        # theta_history.append(theta)          # 傳進(jìn)列表
        theta = theta - eta * DJ(theta,X_b,y)        # 計(jì)算下次的theta
        # 判斷上次的J值和本次的J值差距是否小于最小值
        if abs(J(theta,X_b,y) - J(last,X_b,y)) < epsilon:
            break
        # 循環(huán)次數(shù)+1
        i_iter += 1

    return theta
4.生成數(shù)據(jù)米愿,測(cè)試梯度下降
'''生成數(shù)據(jù)'''
np.random.seed(666)
x = 2 * np.random.random(size=100)
# 修改為100行1列的二維數(shù)組,也就是100個(gè)數(shù)據(jù)厦凤,每個(gè)數(shù)據(jù)1個(gè)特征
X = x.reshape(-1,1)
# 生成100個(gè)靠近y = 3x + 4 直線的點(diǎn)
y = x * 3. + 4. + np.random.normal(size=100)
# 將X修改其中數(shù)據(jù)值為X_b
ones = np.ones((len(X),1))
X_b = np.hstack([ones,X])
print(X_b[:5])
# [[1.         1.40087424]
#  [1.         1.68837329]
#  [1.         1.35302867]
#  [1.         1.45571611]
#  [1.         1.90291591]]

# 設(shè)置init_theta,一個(gè)列向量,其長(zhǎng)度與數(shù)據(jù)集X_b的維度(列數(shù))相同
# 不能是X育苟,因?yàn)閠heta向量包含theta(0)
init_theta = np.zeros(X_b.shape[1])
print(init_theta)
# [0. 0.]

eta = 0.01

如果梯度下降法尋找出來(lái)的theta(0)接近4,theta(1)接近3较鼓,那么就說(shuō)明找到了。

theta = gradient_descent_many(X_b,y,init_theta,eta)
print(theta)
# [4.02145786 3.00706277]

成功Nグ亍2├谩!

5.整體代碼
import numpy as np
import matplotlib.pyplot as plt

# 損失函數(shù)J
def J(theta,X_b,y):
    try:
        return np.sum((y - X_b.dot(theta))**2)/len(X_b)
    except:
        return float('inf')

def DJ(theta,X_b,y):
    res = np.empty(len(theta))
    # 第一個(gè)元素
    res[0] = np.sum(X_b.dot(theta) - y)
    # 第二個(gè)往后
    for i in range(1,len(theta)):
        res[i] = (X_b.dot(theta) - y).dot(X_b[:,i])

    return res *2 / len(X_b)

def gradient_descent_many(X_b,y,init_theta,eta,n_iters=1e4,epsilon=1e-8):
    '''

    :param X_b: 數(shù)據(jù)集漱竖,經(jīng)過(guò)改造(第0列都為1)
    :param y:  數(shù)據(jù)集對(duì)應(yīng)正確值
    :param init_theta: 初始點(diǎn)的theta值禽篱,一個(gè)向量
    :param eta:  學(xué)習(xí)率
    :param n_iters: 循環(huán)次數(shù),默認(rèn)為1e4
    :param epsilon:  最小差距值,默認(rèn)為1e-8
    :return:
    :return:
    '''
    # 將初始化的init_theta賦給theta
    theta = init_theta
    i_iter = 0       # 初始化循環(huán)次數(shù)
    # theta_history = []      #  初始化路徑列表

    while i_iter < n_iters:        # 當(dāng)循環(huán)次數(shù)小于參數(shù)n_iter時(shí),執(zhí)行循環(huán)
        last = theta             # 記錄本次的theta
        # theta_history.append(theta)          # 傳進(jìn)列表
        theta = theta - eta * DJ(theta,X_b,y)        # 計(jì)算下次的theta
        # 判斷上次的J值和本次的J值差距是否小于最小值
        if abs(J(theta,X_b,y) - J(last,X_b,y)) < epsilon:
            break
        # 循環(huán)次數(shù)+1
        i_iter += 1

    return theta

'''生成數(shù)據(jù)'''
np.random.seed(666)

x = 2 * np.random.random(size=100)
# 修改為100行1列的二維數(shù)組,也就是100個(gè)數(shù)據(jù),每個(gè)數(shù)據(jù)1個(gè)特征
X = x.reshape(-1,1)
# 生成100個(gè)靠近y = 3x + 4 直線的點(diǎn)
y = x * 3. + 4. + np.random.normal(size=100)
# 將X修改其中數(shù)據(jù)值為X_b
ones = np.ones((len(X),1))
X_b = np.hstack([ones,X])
print(X_b[:5])
# [[1.         1.40087424]
#  [1.         1.68837329]
#  [1.         1.35302867]
#  [1.         1.45571611]
#  [1.         1.90291591]]

# 設(shè)置init_theta,一個(gè)列向量馍惹,其長(zhǎng)度與數(shù)據(jù)集X_b的維度(列數(shù))相同
# 不能是X躺率,因?yàn)閠heta向量包含theta(0)
init_theta = np.zeros(X_b.shape[1])
print(init_theta)
# [0. 0.]
eta = 0.01

theta = gradient_descent_many(X_b,y,init_theta,eta)
print(theta)
# [4.02145786 3.00706277]

3. 封裝梯度下降函數(shù)-->線性回歸類(lèi)

在之前我們寫(xiě)的線性回歸的類(lèi)中。我們的fit方法是通過(guò)公式推導(dǎo)的方式計(jì)算theta的万矾,而梯度下降法是通過(guò)不斷迭代尋找最好的theta的悼吱。

fit_gd():梯度下降法函數(shù),在該函數(shù)中封裝了三個(gè)函數(shù):

  • J():計(jì)算損失函數(shù)
  • DJ():計(jì)算損失函數(shù)的導(dǎo)
  • gradient_descent():尋找最佳theta
  • fit_gd()的參數(shù)為原始訓(xùn)練數(shù)據(jù)良狈,而這三個(gè)內(nèi)部函數(shù)的參數(shù)數(shù)據(jù)為經(jīng)過(guò)處理的數(shù)據(jù)后添,所以在內(nèi)部函數(shù)外要進(jìn)行數(shù)據(jù)整理(X_b)以及參數(shù)(init_theta,eta)的設(shè)置。
class LinearRegression:

    def __init__(self):
        """初始化Linear Regression模型"""

        ## 系數(shù)向量(θ1,θ2,.....θn)
        self.coef_ = None
        ## 截距 (θ0)
        self.interception_ = None
        ## θ向量
        self._theta = None

    '''線性回歸公式訓(xùn)練模型们颜,計(jì)算theta'''
    def fit_normal(self, X_train, y_train):
        # 拼接為X(b)格式的數(shù)據(jù)吕朵,-----在每行的第一列之前加上1.
        ones_vector = np.ones((len(X_train), 1))
        X_b = np.hstack([ones_vector, X_train])

        # 根據(jù)X_b帶入公式計(jì)算w
        # arr.dot(arr):點(diǎn)乘
        # np.linalg.inv(arr):矩陣的逆
        self._theta = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y_train)

        self.coef_ = self._theta[1:]
        self.interception_ = self._theta[0]

        return self

    '''使用梯度下降尋找theta'''
    def fit_gd(self, X_train, y_train, eta=0.01, n_iters=1e4):
        """根據(jù)訓(xùn)練數(shù)據(jù)集X_train, y_train, 使用梯度下降法訓(xùn)練Linear Regression模型"""
        assert X_train.shape[0] == y_train.shape[0], \
            "the size of X_train must be equal to the size of y_train"

        def J(theta, X_b, y):
            try:
                return np.sum((y - X_b.dot(theta)) ** 2) / len(y)
            except:
                return float('inf')

        def dJ(theta, X_b, y):
            res = np.empty(len(theta))
            res[0] = np.sum(X_b.dot(theta) - y)
            for i in range(1, len(theta)):
                res[i] = (X_b.dot(theta) - y).dot(X_b[:, i])
            return res * 2 / len(X_b)

        def gradient_descent(X_b, y, initial_theta, eta, n_iters=1e4, epsilon=1e-8):

            theta = initial_theta
            cur_iter = 0

            while cur_iter < n_iters:
                gradient = dJ(theta, X_b, y)
                last_theta = theta
                theta = theta - eta * gradient
                if (abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon):
                    break

                cur_iter += 1

            return theta

        X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
        initial_theta = np.zeros(X_b.shape[1])
        self._theta = gradient_descent(X_b, y_train, initial_theta, eta, n_iters)

        self.interception_ = self._theta[0]
        self.coef_ = self._theta[1:]

        return self


    '''測(cè)試集'''
    def perdict(self, X_perdict):
        ones_vector = np.ones((len(X_perdict), 1))
        X_b = np.hstack([ones_vector, X_perdict])

        y_head = X_b.dot(self._theta)

        return y_head

    '''MSE'''
    def mean_squared_error(self, y_true, y_predict):
        """計(jì)算y_true和y_predict之間的MSE"""
        assert len(y_true) == len(y_predict), \
            "the size of y_true must be equal to the size of y_predict"

        return np.sum((y_true - y_predict) ** 2) / len(y_true)

    '''R^2'''
    def r2_score(self, y_true, y_perdict):
        r2_score = 1 - self.mean_squared_error(y_true, y_perdict) / np.var(y_true)
        return r2_score

    def __repr__(self):
        return 'LinearRegression'

調(diào)用:

'''生成數(shù)據(jù)'''
np.random.seed(666)

x = 2 * np.random.random(size=100)
# 修改為100行1列的二維數(shù)組,也就是100個(gè)數(shù)據(jù),每個(gè)數(shù)據(jù)1個(gè)特征
X = x.reshape(-1,1)
# 生成100個(gè)靠近y = 3x + 4 直線的點(diǎn)
y = x * 3. + 4. + np.random.normal(size=100)

# 實(shí)例化算法模型
lin = LinearRegression()
lin.fit_gd(X,y)
print(lin.coef_)        # [3.00706277]
print(lin.interception_)        # 4.021457858204859
print(lin._theta)      # [4.02145786 3.00706277]

訓(xùn)練結(jié)果大概正確窥突。

4.向量化

我們?cè)谟?jì)算多元線性回歸的的損失函數(shù)J對(duì)每個(gè)theta值的導(dǎo)DJ時(shí)略顯麻煩努溃,先計(jì)算第一個(gè),在循環(huán)計(jì)算其他得數(shù)據(jù)項(xiàng)阻问,如果把這個(gè)DJ用向量的點(diǎn)乘表示出來(lái)梧税,那么就輕松了。

推導(dǎo):

由于X(b)的第0列的所有數(shù)據(jù)都是1,所以上式成立第队。

  1. 觀察上式右邊的式子,先不看 2/m:
    它是一個(gè)n+1 * 1 格式的向量哮塞,其每一項(xiàng)都是m個(gè)標(biāo)量求和,那么去掉求和符號(hào)的話:所以其每一項(xiàng)必然是一個(gè)行向量(1 * m)(點(diǎn)乘)一個(gè)列向量(m * 1),并且他們每一個(gè)行式子的區(qū)別都是右邊向量X_b的列數(shù)變化
  • 那么我們呢可以將左邊看作一個(gè)列向量(m * 1)凳谦,右邊看作一個(gè)矩陣忆畅,該矩陣就是X_b矩陣的轉(zhuǎn)置矩陣(n+1 * m)。

展開(kāi)來(lái)寫(xiě):

這樣計(jì)算出來(lái)就是DJ了尸执。

修改DJ函數(shù):

def DJ(theta,X_b,y):
    return X_b.T.dot(X_b.dot(theta) - y)* 2. / len(X_b)

使用boston房?jī)r(jià)進(jìn)行測(cè)試:

from sklearn import datasets
from KNN.knn_iris import train_test_split
from Tidudown.manyTidu.tuduClass import LinearRegression

data = datasets.load_boston()
X = data.data
y = data.target
X = X[y<50]
y = y[y<50]

X_train,y_train,x_test,y_test = train_test_split(X,y)

lr = LinearRegression()

lr.fit_gd(X_train,y_train,eta = 0.000001,n_iters=1e6)
y_perdict = lr.perdict(x_test)
z = lr.r2_score(y_test,y_perdict)
print(z)
# 0.6908063505166206

使用公式推導(dǎo)的方式計(jì)算theta訓(xùn)練模型:

# 使用公式推導(dǎo)的方式訓(xùn)練模型
lr.fit_normal(X_train,y_train)
y_perdict = lr.perdict(x_test)
z = lr.r2_score(y_test,y_perdict)
print(z)
# 0.784704968177089

在使用真實(shí)的數(shù)據(jù)家凯,調(diào)整eta和n_iters,

  • 要么由于eta太小導(dǎo)致無(wú)法得出真實(shí)的結(jié)果如失,導(dǎo)致學(xué)習(xí)不好绊诲,正確率低
  • 要么由于eta太大導(dǎo)致訓(xùn)練時(shí)間加長(zhǎng),這是由于數(shù)據(jù)的規(guī)模在不同的特征上不同褪贵,所以我們需要對(duì)數(shù)據(jù)進(jìn)行歸一化掂之。

5.數(shù)據(jù)歸一化

為了防止數(shù)據(jù)中某一列數(shù)據(jù)的值過(guò)大導(dǎo)致結(jié)果發(fā)生偏差,要進(jìn)行數(shù)據(jù)歸一化操作脆丁。

from sklearn import datasets
from KNN.knn_iris import train_test_split
from Tidudown.manyTidu.tuduClass import LinearRegression
from sklearn.preprocessing import StandardScaler

data = datasets.load_boston()
X = data.data
y = data.target
X = X[y<50]
y = y[y<50]

# 分割數(shù)據(jù)
X_train,y_train,x_test,y_test = train_test_split(X,y)

# 數(shù)據(jù)歸一化
ss = StandardScaler()
ss.fit(X_train)
X_train_standard = ss.transform(X_train)
x_test_standard = ss.transform(x_test)

# 開(kāi)始學(xué)習(xí)
lin = LinearRegression()
# 梯度下降
lin.fit_gd(X_train_standard,y_train)
y_perdict = lin.perdict(x_test_standard)
re = lin.r2_score(y_test,y_perdict)
print(re)
# 0.7876525859547368
# 公式推導(dǎo)
lin.fit_normal(X_train,y_train)
y_perdict = lin.perdict(x_test)
z = lin.r2_score(y_test,y_perdict)
print(z)
# 0.7876600935022398

可以看到使用兩種方式學(xué)習(xí)后的正確率基本都差不多了世舰。

6.梯度下降法的優(yōu)勢(shì)

自己定義一個(gè)訓(xùn)練集,theta向量偎快,以及他們之間的線值y冯乘。

m = 1000
n = 5000

big_X = np.random.normal(size=(m, n))
true_theta = np.random.uniform(0.0, 100.0, size=n+1)
big_y = big_X.dot(true_theta[1:]) + true_theta[0] + np.random.normal(0., 10., size=m)

big_reg1 = LinearRegression()
%time big_reg1.fit_normal(big_X, big_y)

# CPU times: user 18.8 s, sys: 899 ms, total: 19.7 s
# Wall time: 10.9 

big_reg2 = LinearRegression()
%time big_reg2.fit_gd(big_X, big_y)

# CPU times: user 9.51 s, sys: 121 ms, total: 9.63 s
# Wall time: 5.76 s

如果樣本數(shù)非常多,那么即使使用梯度下降法也會(huì)導(dǎo)致速度比較慢晒夹,因?yàn)樵谔荻认陆捣ㄖ旭陕琂的值需要每一個(gè)樣本都要參與運(yùn)算。這時(shí)候需要采用隨機(jī)梯度下降法丐怯。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末喷好,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子读跷,更是在濱河造成了極大的恐慌梗搅,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件效览,死亡現(xiàn)場(chǎng)離奇詭異无切,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)丐枉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)哆键,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人瘦锹,你說(shuō)我怎么就攤上這事籍嘹∩量” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵辱士,是天一觀的道長(zhǎng)泪掀。 經(jīng)常有香客問(wèn)我,道長(zhǎng)颂碘,這世上最難降的妖魔是什么异赫? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮凭涂,結(jié)果婚禮上祝辣,老公的妹妹穿的比我還像新娘贴妻。我一直安慰自己切油,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布名惩。 她就那樣靜靜地躺著澎胡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪娩鹉。 梳的紋絲不亂的頭發(fā)上攻谁,一...
    開(kāi)封第一講書(shū)人閱讀 52,457評(píng)論 1 311
  • 那天,我揣著相機(jī)與錄音弯予,去河邊找鬼戚宦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛锈嫩,可吹牛的內(nèi)容都是我干的受楼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼呼寸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼艳汽!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起对雪,我...
    開(kāi)封第一講書(shū)人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤河狐,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后瑟捣,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體馋艺,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年迈套,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捐祠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡交汤,死狀恐怖雏赦,靈堂內(nèi)的尸體忽然破棺而出劫笙,到底是詐尸還是另有隱情,我是刑警寧澤星岗,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布填大,位于F島的核電站,受9級(jí)特大地震影響俏橘,放射性物質(zhì)發(fā)生泄漏允华。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一寥掐、第九天 我趴在偏房一處隱蔽的房頂上張望靴寂。 院中可真熱鬧,春花似錦召耘、人聲如沸百炬。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)剖踊。三九已至,卻和暖如春衫贬,著一層夾襖步出監(jiān)牢的瞬間德澈,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工固惯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留梆造,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓葬毫,卻偏偏與公主長(zhǎng)得像镇辉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子供常,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360