利用BP神經網絡進行函數擬合

摘要

數據擬合是在假設模型結構已知的條件下最優確定模型中未知參數使預測值與數據吻合度最高,本文選取線性項加激活函數組成一個非線性模型,利用神經網絡算法最優確定模型中的未知參數,利用隨機搜索的方式確定函數模型,從而達到很好的擬合效果

關鍵詞

BP神經網絡 隨機搜索 隨機重啓 參數優化 數據擬合 RELU

問題描述

數據擬合問題普遍存在,擬合問題本質上是在假設模型結構已知的條件下最優確定模型中未知參數以使得模型預測與數據的吻合度最高,即數據擬合問題往往描述爲一個參數優化問題。
複雜工業過程對於模型精度的要求,線性模型往往不能滿足。因此模型結構假設爲非線性更爲合理。此處我們假設模型結構爲:

算法設計

BP神經網絡是一種按照誤差逆向傳播訓練的多層前饋神經網絡。神經網絡理論上可以擬合任意曲線,這一點已經得到了嚴格的數學證明。

  1. 三層神經網絡模型
    通常一個多層神經網絡由L層構成,其中有1層輸出層,1層輸入層,L-2層隱藏層。輸入層是神經網絡的第一層,表示一列矩陣或多列矩陣的輸入。輸出層是神經網絡的最後一層,表示網絡的輸出結果,通常是一列矩陣。隱藏層有L-2層,表示每一層神經元通過前向傳播算法計算的結果矩陣。
    在本程序中,採用3層神經網絡。輸入矩陣X=[X1,X2],題目中給出的模型需要對輸入矩陣X進行增廣,然後乘以參數在這裏插入圖片描述,其實相當於神經網絡中的偏置項bi,i=1,2。因此在本程序中無需對輸入矩陣進行增廣。wi,wi, i=1,2;bi,i=1,2分別是輸入層與隱藏層之間,隱藏層與輸出層之間的連接權重和偏置項。設在這裏插入圖片描述,n=1,i=1,2,3,…,M,表示隱藏層的第i個神經元的輸出。
  2. 激活函數
    本程序中採用的激活函數爲RELU函數,即函數模型中的,目的是去除整個神經網絡輸出的線性化,使得整個神經網絡模型呈非線性化。
  3. 損失函數
    本程序中採用的損失函數爲MSE(均方誤差),利用均方誤差和激活函數的導數來更新權重和偏置。
    在這裏插入圖片描述
    前向傳播算法是指依次向前計算相鄰隱藏層之間的連接輸出,直到模型的最終輸出值。其特點作用於相鄰層的兩個神經元之間的計算,且前一層神經元的輸出是後一層的神經元的輸入。
    設對於第n層的第i個神經元,有n-1層的M(M爲超參數)個神經元與該神經元有突觸相連,則第n層第i個神經元的輸入爲:在這裏插入圖片描述
    第n層的第i個神經元的輸出爲:在這裏插入圖片描述
  4. 反向傳播算法
    結合前向傳播的兩個式子,得到第n層第i個神經元的輸入與第n-1層第i個神經元的輸出的關係爲:在這裏插入圖片描述
  5. 梯度下降算法
    得到每一個隱藏層的連接權重和偏置項的梯度後,利用學習率和梯度下降算法更新每一層的連接權重和偏置項:在這裏插入圖片描述
  6. 學習率更新策略
    在配合梯度下降優化的過程中,如果學習率設置過大,則容易導致模型過擬合;如果設置過小,會使得模型優化的速度變得很緩慢。爲此加入衰減因子和學習次數來計算模型每次學習的學習率:
    在這裏插入圖片描述
  7. 歸一化與反歸一化
    在這裏插入圖片描述
    在這裏插入圖片描述
  8. 隨機重啓算法
    爲了防止更新參數時陷入局部極小,採用隨機重啓的方式更換初始參數狀態。
  9. 超參數M的確定
    本程序的超參數M的確定方法爲隨機搜索法,最終確定M的取值爲20。
  10. 編程環境和程序主要函數說明
    全部內容均在JetBrains發佈的PyCharm社區版(版本號2019.2.5)編程環境中完成,使用的語言爲Python,解釋器爲Python3.8。

#產生數據,並將產生的數據方陣降維,方便計算處理
def init(inpit_n_row):

#激活函數
def relu(x):

#激活函數RELU的導數
def d_relu(x):

#歸一化數據
def normalize(data):

#反歸一化數據
def d_normalize(norm_data, data):

#計算損失函數:平方損失函數
def quadratic_cost(y_, y):

#評價函數RSSE
def rsse(y_predict, y):

#前向傳播算法
def prediction(l0, W, B):

#反向傳播算法:根據損失函數優化各個隱藏層的權重
def optimizer(Layers, W, B, y, learn_rate):

#訓練BP神經網絡
def train(X, y, W, B, method, learn_rate):

#擬合曲面可視化
def visualize(y, inpit_n_row, input_n_col, hidden_n_1):

#爲了解決陷入局部極小值,隨機重啓
def random_restart():

#main()函數
if name == ‘main’:

結果分析與討論

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
對於3232的訓練集,需要幾千次的迭代,即可使模型的RSSE值降到0.1以下,需要10000-32000次左右的迭代,即可使模型的RSSE值降到0.001以下;對於1010的測試集,需要30-90次的迭代即可使其RSSE值降到0.1以下,需要200-300次左右的迭代,即可使其的RSSE值降到0.001以下。
(2)抗噪測試
加入高斯白噪聲:a = (np.random.random() - 0.5)/5
在這裏插入圖片描述
在這裏插入圖片描述
加入野值:
b = (np.random.randint(0, 9) - 0.5)/ 9
post = np.random.randint(0, inpit_n_row - 1)
y[post] += b
在這裏插入圖片描述
在這裏插入圖片描述
由此可見,模型的抗噪能力良好。

結論

  1. 神經網絡具有實現任何複雜非線性映射的功能。
  2. BP神經網絡的的學習速度慢,因其本質上是梯度下降法,必然會出現鋸齒現象。
  3. 隨機重啓、模擬退火這些算法可以有效地解決陷入局部極值的問題。

Python源代碼

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['font.sans-serif'] = ['SimHei']


#產生數據,並將產生的數據方陣降維,方便計算處理
def init(inpit_n_row):
    X1 = np.linspace(-2, 2, int(inpit_n_row**0.5))
    X2 = np.linspace(-2, 2, int(inpit_n_row**0.5))
    X1, X2 = np.meshgrid(X1, X2)
    X1 = X1.reshape(-1)
    X2 = X2.reshape(-1)
    x, y = [], []
    for i in range(inpit_n_row):
        # 增加高斯白噪聲
        #a = np.random.random() - 0.5
        #a /= 5
        x.append([X1[i], X2[i]])
        Y = (1 + np.sin(2 * X1[i] + 3 * X2[i])) / (3.5 + np.sin(X1[i] - X2[i])) # + a
        y.append(Y)
    # 增加野值
    '''b = np.random.randint(0, 9) - 0.5
    b /= 9
    post = np.random.randint(0, inpit_n_row - 1)
    y[post] += b'''
    x = np.array(x)
    y = np.array([y]).T
    return x,y


# 激活函數
def relu(x):
    return np.maximum(0,x)

# 激活函數RELU的導數
def d_relu(x):
    x[x<0] = 0
    return x


# 歸一化數據
def normalize(data):
    data_min, data_max = data.min(), data.max()
    data = (data - data_min) / (data_max - data_min)
    return data


# 反歸一化數據
def d_normalize(norm_data, data):
    data_min, data_max = data.min(), data.max()
    return norm_data * (data_max - data_min) + data_min


# 計算損失函數:平方損失函數
def quadratic_cost(y_, y):
    inpit_n_row = len(y)
    return np.sum(1 / inpit_n_row * np.square(y_ - y))


# 評價函數RSSE
def rsse(y_predict, y):
    y_ = np.mean(y)
    return np.sum(np.square(y_predict - y))/np.sum(np.square(y-y_))


# 前向傳播算法
def prediction(l0, W, B):
    Layers = [l0]
    for i in range(len(W)):
        Layers.append(relu(x=Layers[-1].dot(W[i]) + B[i]))
    return Layers


# 反向傳播算法:根據損失函數優化各個隱藏層的權重
def optimizer(Layers, W, B, y, learn_rate):
    l_error_arr = [(y - Layers[-1]) * d_relu(x=Layers[-1])]
    for i in range(len(W) - 1, 0, -1):
        l_error_arr.append(l_error_arr[-1].dot(W[i].T) * d_relu(x=Layers[i]))
    j = 0  # l_delta_arr = [err3, err2, err1]
    for i in range(len(W) - 1, -1, -1):
        W[i] += learn_rate * Layers[i].T.dot(l_error_arr[j])  # W3 += h2 * err3
        B[i] += learn_rate * l_error_arr[j]  # B3 += err3
        j += 1


# 訓練BP神經網絡
def train(X, y, W, B, method, learn_rate):
    #norm_X, norm_y = normalize(data=X), normalize(data=y)  # 歸一化處理
    norm_X = X
    norm_y = y  # 由於測試模型無負值,無需歸一化處理,可極大提高效率

    #加了噪聲之後,需降低精度要求,降低至0.1即可
    end_loss = 0.001
    step_arr, loss_arr, rsse_arr = [], [], []
    step = 1
    if method == '訓練集':
        error = '偏差'
    else:
        error = '方差'
    while True:
        Layers = prediction(l0=norm_X, W=W, B=B)
        rsse_now = rsse(y_predict=Layers[-1], y=norm_y)
        learn_rate_decay = learn_rate # * 1.0 / (1 + step * 0.001)
        optimizer(Layers=Layers, W=W, B=B, y=y, learn_rate=learn_rate_decay)
        cur_loss = quadratic_cost(y_=Layers[-1], y=norm_y)
        if step % 1000 == 0:
            step_arr.append(step)
            loss_arr.append(cur_loss)
            rsse_arr.append(rsse_now)
            print('經過{}次迭代,{}當前{}爲{},RSSE爲{}'.format(step, method, error, cur_loss, rsse_now))
        if rsse_now < end_loss:
            # prediction_ys = d_normalize(norm_data=Layers[-1], data=y)  # 反歸一化結果
            print('經過{}次迭代,{}最終{}爲{},RSSE爲{}'.format(step, method, error, cur_loss, rsse_now))
            return 0, Layers[-1]
        if len(rsse_arr) >= 2 and rsse_arr[-2] - rsse_arr[-1] < 1e-5 or step > 50000:
            print('此初始權重可能導致算法陷入局部極值,故隨機重啓,重新迭代')
            return -1, []
        step += 1


# 擬合曲面可視化
def visualize(y, inpit_n_row, input_n_col, hidden_n_1):
    z = []
    for i in y:
        for j in i:
            z.append(j)
    z = np.array(z).reshape(int(inpit_n_row**0.5), int(inpit_n_row**0.5))
    # 代入訓練好的模型測試
    flag = -1
    while flag == -1:
        np.random.seed(random_restart())
        # 產生測試集
        test_n_row = 100
        x_test, y_test = init(test_n_row)
        W1 = np.random.randn(input_n_col, hidden_n_1) / np.sqrt(test_n_row)
        b1 = np.zeros((test_n_row, hidden_n_1))
        # 輸出層權重矩陣
        W2 = np.random.randn(hidden_n_1, 1) / np.sqrt(test_n_row)
        b2 = np.zeros((test_n_row, 1))
        w, b = [W1, W2], [b1, b2]
        flag, y_test = train(x_test, y_test, w, b, '測試集', learn_rate=0.15)
    z_test = []
    for i in y_test:
        for j in i:
            z_test.append(j)
    z_test = np.array(z_test).reshape(int(test_n_row ** 0.5), int(test_n_row ** 0.5))
    # 擬合曲面可視化
    X1 = np.linspace(-2, 2, int(inpit_n_row**0.5))
    X2 = np.linspace(-2, 2, int(inpit_n_row**0.5))
    X1, X2 = np.meshgrid(X1, X2)
    Z = (1 + np.sin(2 * X1 + 3 * X2)) / (3.5 + np.sin(X1 - X2))
    fig = plt.figure()
    ax = Axes3D(fig)
    # 畫出真實函數
    ax.set_xlabel('X1')
    ax.set_ylabel('X2')
    ax.set_zlabel('y')
    ax.plot_surface(X1, X2, Z, color='red')
    # 畫出預測函數
    fig = plt.figure()
    ax = Axes3D(fig)
    ax.set_xlabel('X1')
    ax.set_ylabel('X2')
    ax.set_zlabel('y_predict')
    ax.plot_surface(X1, X2, z, color='blue')
    # 畫出測試結果
    X1 = np.linspace(-2, 2, int(test_n_row ** 0.5))
    X2 = np.linspace(-2, 2, int(test_n_row ** 0.5))
    X1, X2 = np.meshgrid(X1, X2)
    fig = plt.figure()
    ax = Axes3D(fig)
    ax.set_xlabel('X1')
    ax.set_ylabel('X2')
    ax.set_zlabel('y_test')
    ax.plot_surface(X1, X2, z_test, color='yellow')
    plt.show()


# 爲了解決陷入局部極小值,隨機重啓
def random_restart():
    return np.random.randint(0, 23768)


if __name__ == '__main__':
    # 樣本點個數
    inpit_n_row = 1024
    # 輸入維度
    input_n_col = 2
    # 神經元個數
    hidden_n_1 = 20
    flag = -1
    while flag == -1:
        np.random.seed(random_restart())
        x, y = init(inpit_n_row)
        # 第一個隱藏層權重矩陣
        W1 = np.random.randn(input_n_col, hidden_n_1) / np.sqrt(inpit_n_row)
        b1 = np.zeros((inpit_n_row, hidden_n_1))
        # 輸出層權重矩陣
        W2 = np.random.randn(hidden_n_1, 1) / np.sqrt(inpit_n_row)
        b2 = np.zeros((inpit_n_row, 1))
        W, B = [W1, W2], [b1, b2]
        flag, y = train(x, y, W, B, '訓練集', learn_rate=0.001)  # 開始訓練BP神經網絡
    visualize(y, inpit_n_row, input_n_col, hidden_n_1)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章