機器學習:梯度下降和牛頓法

一、問題描述

考慮將基本梯度下降和牛頓法應用到表中的數據上。

(a)用這兩種算法對二維數據給出 的判別。對梯度下降法取 。畫出以迭代次數爲準則函數的曲線。

(b)估計這兩種方法的數學運算量。

(c)畫出收斂時間-學習率曲線。求出無法收斂的最小學習率。

二、算法核心思想分析

1、線性判別函數

的各個分量的線性組合而成的函數:

這裏 是“權向量”, 被稱爲“閾值權”。對於二分類器來說,若 ,則判定爲 ,若 ,則判定爲 。方程 定義了一個判定面,把兩個類分開,被稱爲“超平面”。

2、廣義線性判別函數

線性判別函數 可寫成:

其中係數 是權向量 的分量。通過加入另外的項( 的各對向量之間的乘積),我們得到二次判別函數:

因爲 ,不失一般性我們可以假設 。由此,二次判別函數擁有更多係數來產生複雜的分隔面。此時 定義的分隔面試一個二階曲面或說是“超二次曲面”。

若繼續加入更高次的項,我們就得到多項式判別函數。這可看做對某一判別函數 做級數展開,然後取其截尾逼近,此時廣義線性判別函數可寫成:

這裏 通常被稱爲“增廣特徵向量”,類似地, 被稱爲“增廣權向量”,設 ,可寫成:

對於兩類線性可分的情況,使用判別函數 來劃分 ,若 ,則判定爲 ,若 ,則判定爲

通常引入邊界裕量 限制解區域,要求解向量滿足:

3、梯度下降

我們在尋找滿足線性不等式組 的解時所採用的方法是:定義一個準則函數 ,當 是解向量時, 爲最小。這樣就將問題簡化爲一個標量函數的極小化問題——通常可用梯度下降法來解決。梯度下降的原理非常簡單,首先從一個任意選擇的權向量 開始,計算其梯度向量 ,下一個值 由自 向下降最陡的方向移一段距離而得到,即沿梯度的負方向。通常 由等式

計算, 是正的比例因子,或者說是用於設定步長的“學習率”(learning rate)。我們希望得到這樣一個權向量序列:最終收斂到一個使 極小化的解上。

步驟:初始化權向量 、閾值 和學習率 ,不斷迭代更新 ,直到 ,使得準則函數達到一個極小值, 收斂。

4、牛頓法

牛頓法權向量的更新公式爲:

其中, 爲準則函數的赫森矩陣。收斂條件爲:

因爲牛頓法使用了準則函數的二次偏導,因此牛頓法比梯度下降每一步都給出了更好的步長,也就更快收斂。但是每次遞歸都要計算赫森矩陣 的逆,時間複雜度爲 ,運算量更大。

三、題目分析

1、題目分析

本題表格中給出用於求判別函數中解向量的數據,二維數據 共20組。需要假設用於判別的準則函數,使用梯度下降算法和牛頓法分別與準則函數結合,求出解向量,從而求得線性判別函數。並畫出以迭代次數爲準則函數的曲線。

本題中假設準則函數爲:

梯度如下:

其中 爲解向量, 爲訓練數據, 爲邊界裕量。

2、梯度下降

使用LMS算法進行迭代。學習率設爲0.001,閾值設爲0.01,沿負梯度方向更新權值,最後畫出【迭代次數-準則函數曲線】、【分類界面】、【學習率-迭代次數曲線】,並得出無法收斂的最小學習率。

3、牛頓法

黑塞矩陣H由準則函數的二階偏導求得,爲 ,代入樣本計算即可,將其求逆並與梯度相乘,更新權重。最後畫出【迭代次數-準則函數曲線】和【分類界面】。

4、Notice:

  • 學習率 的選擇對於梯度下降算法收斂至關重要。如果 太小,收斂將非常慢;如果 太大的話可能會過沖(overshoot),甚至發散。
  • 對於數據的使用需要注意,不是直接利用原始數據進行訓練,而是將原始數據增加一列,將其變成增廣矩陣。
  • 邊界裕量 的取值任意
  • 牛頓法中學習率爲 ,所以需要赫森矩陣 是非奇異矩陣。

四、代碼及運行結果

1、梯度下降

# -*- coding:utf-8 -*-
import xlrd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False


# 讀取數據
def read_data():
    x = []
    data = xlrd.open_workbook("lab4_data.xlsx")
    table = data.sheets()[0]
    rows = table.nrows
    for i in range(1, rows):
        row_value = table.row_values(i)
        if row_value[2] == 1 or row_value[2] == 3:
            x.append(row_value)
    return x


# 準則函數
def get_j(a, b, y):
    j = 0.5 * np.power(np.linalg.norm((y * a - b)), 2)
    print("j", j)
    return j


# 準則函數的梯度
def get_gradient(a, b, y):
    gradient = y.T * (y * a - b)
    print("gradient", np.shape(gradient), "\n", gradient)
    return gradient


# 權向量的變化
def get_delta(eta, a, b, y):
    gradient = get_gradient(a, b, y)
    delta = eta * gradient
    print("delta", np.shape(delta), "\n", delta)
    return delta


# 解向量
def get_a(a_k, delta):
    a = a_k - delta
    print("a", np.shape(a), "\n", a)
    return a


# 線性判別函數
def get_gx(a, y):
    gx = a.T * y
    return gx


# 梯度下降:傳入學習率,返回收斂時間、準則函數
def gradient_decent(eta, a, b, y, theta):
    iteration = []
    J = []
    count = 0
    loop_max = 100

    j = get_j(a, b, y)
    print("(", count, j, ")")
    iteration.append(count)
    J.append(j)
    delta = get_delta(eta, a, b, y)
    while np.linalg.norm(delta) >= theta:
        count += 1
        if count >= loop_max:
            print("break!")
            break
        delta = get_delta(eta, a, b, y)
        a = get_a(a, delta)  # 更新a
        j = get_j(a, b, y)
        iteration.append(count)
        J.append(j)
        print("(", count, j, ")")

    return iteration, J


def main():
    data = read_data()
    # print("data\n", np.mat(data))
    w = [x[:2] for x in filter(lambda x: x, data)]
    y = list(map(lambda x: [1, *x], w))
    # print("y\n", np.mat(y))

    # 規範化
    w1 = [x[:2] for x in filter(lambda x: x[2] == 1, data)]
    w3 = [x[:2] for x in filter(lambda x: x[2] == 3, data)]
    test = []
    for i in range(len(w1)):
        w1[i].insert(0, 1)
        test.append(w1[i])
    # w3 = (-1 * np.mat(w3)).tolist()
    for i in range(len(w3)):
        w3[i].insert(0, -1)
        test.append(w3[i])
    print(np.mat(test))
    y = test

    # initial
    a = [1, 1, 1]  # 權向量a
    theta = 0.01  # 閾值θ
    eta = 0.001  # 學習率η
    b = [1 for i in range(len(y))]  # 邊界裕量

    y = np.mat(y)
    a = np.mat(a).T
    b = np.mat(b).T

    print("y", np.shape(y), "\n", y)
    print("a", np.shape(a), "\n", a)
    print("b", np.shape(b))  # , "\n", b

    # Gradient Decent
    fig1 = plt.figure(1)

    iteration, J = gradient_decent(eta, a, b, y, theta)
    plt.plot(iteration, J)  # 準則函數

    plt.title(u"學習率η = " + str(eta))
    plt.xlabel(u"迭代次數")
    plt.ylabel(u"準則函數")

    # 繪製樣本點
    fig2 = plt.figure(2)
    w1 = [x[:2] for x in filter(lambda x: x[2] == 1, data)]
    w3 = [x[:2] for x in filter(lambda x: x[2] == 3, data)]
    w1x1 = [x[0] for x in filter(lambda x: x, w1)]
    w1x2 = [x[1] for x in filter(lambda x: x, w1)]
    w3x1 = [x[0] for x in filter(lambda x: x, w3)]
    w3x2 = [x[1] for x in filter(lambda x: x, w3)]

    plt.scatter(w1x1, w1x2, c='r')
    plt.scatter(w3x1, w3x2, c='g')
    plt.xlabel("x1")
    plt.ylabel("x2")
    plt.title(u"分類界面")

    # # 準則函數分隔面
    x = [np.linspace(-5.1, 6.8, 100)]  # [1 for i in range(len(y))],
    x = np.mat(x).T

    x2 = (a[0, 0] + a[1, 0] * x) / a[2, 0]
    plt.plot(x, x2)

    # 學習率-收斂時間
    fig3 = plt.figure(3)
    learn_rate = [0.0005, 0.001, 0.0015, 0.002, 0.0025, 0.003, 0.0035, 0.004, 0.0045, 0.005, 0.0055]
    convergence = []
    for i in range(len(learn_rate)):
        iteration, J = gradient_decent(learn_rate[i], a, b, y, theta)
        convergence.append(iteration[-1])

    print(learn_rate, "\n", convergence)
    plt.plot(learn_rate, convergence)
    for xy in zip(learn_rate, convergence):
        plt.annotate("(%s,%s)" % xy, xy=xy, xytext=(-20, 10), textcoords='offset points')
    plt.xlabel(u"學習率")
    plt.ylabel(u"收斂時間")
    plt.title(u"學習率-收斂時間")

    plt.show()


if __name__ == '__main__':
    main()

【迭代次數-準則函數曲線】

學習率爲0.001時,在所設閾值下,梯度下降16次收斂

【分類界面】

權向量爲[ 0.73775053 -0.19529194 0.23079494 ]

可以看到LMS算法並未給出最優解

【學習率-迭代次數曲線】

當學習率達到0.0045左右時,梯度下降無法收斂

2、牛頓法

# -*- coding:utf-8 -*-
import xlrd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False


# 讀取數據
def read_data():
    x = []
    data = xlrd.open_workbook("lab4_data.xlsx")
    table = data.sheets()[0]
    rows = table.nrows
    for i in range(1, rows):
        row_value = table.row_values(i)
        if row_value[2] == 1 or row_value[2] == 3:
            x.append(row_value)
    return x


# 準則函數
def get_j(a, b, y):
    j = 0.5 * np.power(np.linalg.norm((y * a - b)), 2)
    print("j", j)
    return j


# 準則函數的梯度
def get_gradient(a, b, y):
    gradient = y.T * (y * a - b)
    print("gradient", np.shape(gradient), "\n", gradient)
    return gradient


# 權向量的變化
def get_delta(H, a, b, y):
    gradient = get_gradient(a, b, y)
    delta = H * gradient
    print("delta", np.shape(delta), "\n", delta)
    return delta


# 解向量
def get_a(a_k, delta):
    a = a_k - delta
    print("a", np.shape(a), "\n", a)
    return a


# 線性判別函數
def get_gx(a, y):
    gx = a.T * y
    return gx


def newton(H, a, b, y, theta):
    iteration = []
    J = []
    count = 0
    loop_max = 100

    j = get_j(a, b, y)
    print("(", count, j, ")")
    iteration.append(count)
    J.append(j)
    delta = get_delta(H, a, b, y)
    while np.linalg.norm(delta) >= theta:
        count += 1
        if count >= loop_max:
            print("break!")
            break
        delta = get_delta(H, a, b, y)
        a = get_a(a, delta)  # 更新a
        j = get_j(a, b, y)
        iteration.append(count)
        J.append(j)
        print("(", count, j, ")")
    return iteration, J


def main():
    data = read_data()
    # print("data\n", np.mat(data))
    w = [x[:2] for x in filter(lambda x: x, data)]
    y = list(map(lambda x: [1, *x], w))
    # print("y\n", np.mat(y))

    # 規範化
    w1 = [x[:2] for x in filter(lambda x: x[2] == 1, data)]
    w3 = [x[:2] for x in filter(lambda x: x[2] == 3, data)]
    test = []
    for i in range(len(w1)):
        w1[i].insert(0, 1)
        test.append(w1[i])
    # w3 = (-1 * np.mat(w3)).tolist()
    for i in range(len(w3)):
        w3[i].insert(0, -1)
        test.append(w3[i])
    print(np.mat(test))
    y = test

    # initial
    a = [1, 1, 1]  # 權向量a
    theta = 0.001  # 閾值θ
    H = 0  # 學習率 黑塞矩陣的逆
    b = [1 for i in range(len(y))]  # 邊界裕量

    y = np.mat(y)
    a = np.mat(a).T
    b = np.mat(b).T

    print("y", np.shape(y), "\n", y)
    print("a", np.shape(a), "\n", a)
    print("b", np.shape(b))  # , "\n", b

    H = (y.T * y).I
    print("H", H)

    fig1 = plt.figure(1)

    iteration, J = newton(H, a, b, y, theta)
    plt.plot(iteration, J)  # 準則函數

    plt.xlabel(u"迭代次數")
    plt.ylabel(u"準則函數")
    plt.title(u"迭代次數-準則函數")

    # 繪製樣本點
    fig2 = plt.figure(2)
    w1 = [x[:2] for x in filter(lambda x: x[2] == 1, data)]
    w3 = [x[:2] for x in filter(lambda x: x[2] == 3, data)]
    w1x1 = [x[0] for x in filter(lambda x: x, w1)]
    w1x2 = [x[1] for x in filter(lambda x: x, w1)]
    w3x1 = [x[0] for x in filter(lambda x: x, w3)]
    w3x2 = [x[1] for x in filter(lambda x: x, w3)]

    plt.scatter(w1x1, w1x2, c='r')
    plt.scatter(w3x1, w3x2, c='g')
    plt.xlabel("x1")
    plt.ylabel("x2")
    plt.title(u"分類界面")

    # 準則函數分隔面
    x = [np.linspace(-6, 7, 100)]  # [1 for i in range(len(y))],
    x = np.mat(x).T

    x2 = (a[0, 0] + a[1, 0] * x) / a[2, 0]
    plt.plot(x, x2)

    plt.show()


if __name__ == '__main__':
    main()

【迭代次數-準則函數曲線】

在所設閾值下,牛頓法收斂步數遠小於梯度下降

【分類界面】

權向量爲[ 0.39200249 -0.15039118 0.2144672 ]

牛頓法和梯度下降都是爲了尋找準則函數的極小值,所以兩者最後收斂結果一致,僅在算法複雜度和收斂步數有區別。

3、運算量分析

梯度下降運算量:取決於收斂的步數,即迭代的次數,學習量與迭代次數成正比。

牛頓法運算量:不僅取決於收斂的步數,同時取決於赫森矩陣H 的運算複雜度。

五、總結

基本梯度下降法和牛頓法都能求得最終的權向量,使得準則函數取得一個極小值。一般來說,即使有了最佳的η(k) ,牛頓法也比梯度下降法在每一步都給出了更好的步長,所以收斂速度更快。但是當赫森矩陣H 爲奇異矩陣時就不能用牛頓法了。而且,即使H 是非奇異的,每次遞歸時計算H 逆矩陣所需的 時間可輕易地將牛頓法帶來的好處給抵消了。實際上,將η(k) 設置爲比較小的常數 ,雖然比每一步都使用最優的η(k) 將需要更多步驟來校正,但通常總的時間開銷卻更少。

 

如有錯誤請指正

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