對簡單梯度下降方法的分析總結,有關步長,梯度精度和迭代次數


對簡單梯度下降方法的分析總結,有關步長,梯度精度和迭代次數

我們對一組數據進行簡單函數擬合時,會用到一種基礎方法即梯度下降法

基本原理

  • 現在我們有一組數據

\[x_i, y_i, z_i \]

  • 這些數據之間的關系為

\[w_1 * x_i + w_2 * y_i + b = z_i, w_1, w_2, b為未知的參數 \]

他們之間是函數關系Z(x, y)

  • 現在我們要從現有的這n組數據中進行分析,最終找到一組符合這組數據的w1, w2, b,一開始我們並不清楚這三個參數

    • 首先可以初始化這三個參量都為0,並由此根據現有的x,y值算出我們對於z的預測\(A_i\),設\(\theta\)為向量(w1, w2)

    • 之后對方差函數

      \[J(\theta) = \frac{1}{n}\sum_{i = 1}^n [z_i - (w_1 * x_i + w_2 * y_i + b)]^2 \]

      求梯度
      其梯度為

      \[\theta' = (\frac{\partial J}{\partial w_1}, \frac{\partial J}{\partial w_2}) \]

      有關偏導數的定義較為簡潔,可自行從網上搜索

    事實上梯度所指的方向就是J函數上升最快的方向
    而我們的目標是讓方差J函數盡量小,所以我們需要將\(\theta\)向梯度的反方向\(-\theta'\)變化

    • 當然我們知道,這里的梯度僅僅是(w1, w2)處的梯度,w變化后梯度也會變化,所以我們需要讓w變化一點點,這“一點點”我們用步長“st”表示,即

      \[\theta = \theta - \theta' * st \]

    st由我們自行定義

    • 對於b, 我們這樣進行擬合:

      \[b = b + \frac{1}{n}\sum_{i = 1}^n [z_i - (w_1 * x_i + w_2 * y_i + b)] \]

      這樣可以使b能夠趨於樣本中心

    • 然后重新執行前面的步驟,最終當方差小於某個值或者達到一定次數時,迭代結束,獲得一組符合條件精度的w1, w2, b,完成直線擬合
  • 示例代碼

    • 其中初始參數w1,w2,b被設定為0.5,0.5,5,存放在wn.txt中

    • 十組數據存放於11.csv中,csv文件的x為“math”列,y為“English”列,z為“all”列,按照

    \[all = 0.3 * math + 0.7 * English + 20 \]

    生成(即目標w1 == 0.3,w2 == 0.7, b == 20),如下圖:

    • python代碼如下:

import pandas as pd
import numpy as np


def initww():                   # 初始化w,b
    aa = np.zeros(3, dtype=float)
    with open("wn.txt", "r") as f:
        co = 0
        cc = f.readlines()
        for line in cc:
            aa[co] = float(line)
            co += 1
    return aa


def initcsv(csv):               # 初始化x, y
    aa = np.zeros((len(csv["math"]), 2), dtype=float)
    co = 0
    for maths in csv["math"]:
        aa[co][0] = float(maths)
        co += 1
    co = 0
    for en in csv["English"]:
        aa[co][1] = float(en)
        co += 1
    return aa


def initreals(csv):             # 初始化z
    aa = np.zeros(len(csv["all"]), dtype=float)
    co = 0
    for alls in csv["all"]:
        aa[co] = float(alls)
        co += 1
    return aa


def cost(ws, grade, truth):             # 方差函數
    pred = np.sum(grade * ws[:-1], axis=1) + ws[-1]
    return np.sum((truth - pred) ** 2) / pred.size


def gradient(ws, grade, truth, de):     # 計算梯度,傳入w,b,x,y,z以及現在的方差de
    grad = np.zeros(len(ws), dtype=float)
    for i in range(len(ws) - 1):        # 計算每一分量上的偏導數
        neww = ws.copy()
        neww[i] += 0.000001
        grad[i] = (cost(neww, grade, truth) - de) / 0.000001     # 這里選取dx為0.000001,可以根據需要調整
    return grad


def gd(ws, grade, truth, de, st):       # 梯度下降,傳入w,b,x,y,z以及現在的方差de, 步長st
    rate = gradient(ws, grade, truth, de)
    ws[:-1] -= rate[:-1] * st                           # 調整w
    pred = np.sum(grade * ws[:-1], axis=1) + ws[-1]
    ws[-1] += np.sum(truth - pred) / pred.size          # 調整b
    return ws


if __name__ == "__main__":
    data = pd.read_csv("11.csv")    # 讀取數據並存入array數組中
    ww = initww()                   # ww[:-1]是w1,w2,ww[2]是b
    score = initcsv(data)           # 十組x,y數據
    reals = initreals(data)         # 十個z值

    d = 0.0
    for i in range(100):          # 迭代一百次
        d = cost(ww, score, reals)
        print(ww[0], " ", ww[1], " ", ww[2], " ", "cost:" + str(d))    # 每次輸出w與b和方差
        ww = gd(ww, score, reals, d, 0.001)  # 梯度下降入口,步長設定為0.001

對於步長,dx和迭代次數的分析

  • 上述代碼解決當前問題是可以的,不過在一開始,我的步長,dx與迭代次數分別是0.01,0.01和15
  • 盡管有了理論的支持,在實踐中還是會遇到各種各樣的錯誤現象,而這其中很多都表現為結果發散
    如下圖

    可以看出發散嚴重

這種現象表現為發散至很大的一個數,而且發散是加速的

這種主要是步長選擇不合理,每一步調整之后w的位置距離最優解差的更遠了

  • 我們這里w是0.1的數量級,選取0.01的步長對於它以及當前的方差函數來說,太大了,將步長改為0.001后表現如下

可以看到cost一開始在減小,但之后卻在11.4左右趨於穩定,其他數據也是在一個數附近趨於穩定,當然我們能看到這些數據是和答案相差較多的

這種“死胡同”現象在這里是因為所求梯度不准確所致,我們知道和求導一樣,dx選取越趨近於零,梯度越准確,我們這里選擇的0.01可以說是“太大了”

  • 將dx改為0.001后,結果如下

可以看出,cost降到了0.16,並且其他的參數也與答案很接近,但是這個結果對我們的要求來說可能還不夠,我們需要一個更精確的結果

這里可以看出w,b的變化還可以進行下去,我們需要增加迭代次數

  • 將迭代次數增加到100,結果如下

我們確信再往下應該不會有太多改變了,但是結果仍然有一定差距

不過以上的操作說明減小步長與dx,增加迭代次數確實可以提高預測的精度/曲線的擬合度
但是減小步長的同時我們必須增加迭代次數,由於迭代次數受限限制於當前計算機的算力,步長不可以無限減小,所以為了提高精度,我們應該主要減小dx

  • 將dx改為0.000001,得到如下結果

可以看出,結果可以說是基本正確了,這樣我們的函數就擬合成功了

總結發現

  • 1.步長選取不在於多小,而是在現有條件上選擇最能滿足當前問題的步長,步長減小時盡管精度增加,更不易出現發散錯誤,但是迭代次數要增加(當然不一定同倍數增加)

  • 2.可以說迭代次數決定了我們在現有步長,dx的條件下能達到多小的誤差,在迭代次數增加到一定程度后,這個誤差受制於其他因素,不會再減小

  • 3.而dx越小,梯度越准確,可以想到,越接近最優解(誤差函數的極小值處),梯度越小,而梯度精度就越重要,所以,越小的dx決定了這個程序最終能達到多精確的擬合效果

  • 補充,簡單考慮時,我們可以按上面的程序里一樣,直接用偏導數定義去求梯度,但是這樣一定要選擇一個足夠小的dx才可以。我們對已知解析式的誤差函數一般直接使用它的偏導函數來求梯度,這樣就避免了這個問題,所以上面的程序完全可以依此改寫,當然,如果誤差函數不易得偏導函數,使用定義也是一種方法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM