天天看點

周志華《機器學習》課後習題(第五章):神經網絡

5.1 試述将線性函數

周志華《機器學習》課後習題(第五章):神經網絡

用作神經元激活函數的缺陷。

答:

使用線性函數作為激活函數時,無論是在隐藏層還是在輸出層(無論傳遞幾層),其單元值(在使用激活函數之前)都還是輸入 

周志華《機器學習》課後習題(第五章):神經網絡

的線性組合,這個時候的神經網絡其實等價于邏輯回歸(即原書中的對率回歸,輸出層仍然使用Sigmoid函數)的,若輸出層也使用線性函數作為激活函數,那麼就等價于線性回歸 。

5.2 試述使用圖 5.2(b) 激活函數的神經元與對率回歸的聯系。

使用Sigmoid激活函數,每個神經元幾乎和對率回歸相同,隻不過對率回歸在 

周志華《機器學習》課後習題(第五章):神經網絡
 時輸出為1,而神經元直接輸出 
周志華《機器學習》課後習題(第五章):神經網絡

5.3 對于圖 5.7 中的

周志華《機器學習》課後習題(第五章):神經網絡
,試推導出 BP 算法中的更新公式 (5.13)。
周志華《機器學習》課後習題(第五章):神經網絡
周志華《機器學習》課後習題(第五章):神經網絡

簡單說就是學習率太高會導緻誤差函數來回震蕩,無法收斂;而學習率太低則會收斂太慢,影響訓練效率。

在原書p104也提到過。

5.5 試程式設計實作标準 BP 算法和累積 BP 算法,在西瓜資料集 3.0 上分别用這兩個算法訓練一個單隐層網絡,并進行比較。

标準 BP 算法和累積 BP 算法在原書(P105)中也提到過,就是對應标準梯度下降和随機梯度下降,差别就是後者每次疊代用全部資料計算梯度,前者用一個資料計算梯度。

代碼在:

https://github.com/han1057578619/MachineLearning_Zhouzhihua_ProblemSets/tree/master/ch5--%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/5.5-5.6

具體兩種情況的結果如下圖:可以看出來gd的成本函數收斂過程更加穩定,而sgd每次疊代并不一定向最優方向前進,但總體方向是收斂的,且同樣是疊代200次,最後結果相差不大,但由于sgd每次疊代隻使用一個樣本,計算量大幅度下降,顯然sgd的速度會更快。

ps.關于随機梯度下降的實作,好像有兩種方式,一種是每次将樣本打亂,然後周遊所有樣本,而後再次打亂、周遊;另一種是每次疊代随機抽取樣本。這裡采取的是後一種方式,貌似兩種方式都可以。

此外,BP神經網絡代碼在以前學吳恩達老師深度學習課程的時候就寫過,這次整理了一下正好放上來,是以很多代碼和課程代碼類似,添加了應用多分類的情況的代碼。下面的5.6題也一并在這裡實作。

周志華《機器學習》課後習題(第五章):神經網絡

5.6 試設計一個 BP 改進算法,能通過動态調整學習率顯著提升收斂速度。程式設計實作該算法,并選擇兩個 UCI 資料集與标準 BP 算法進行實驗比較。

動态調整學習率有很多現成的算法,RMSProp、Adam、NAdam等等。也可以手動實作一個簡單指數式衰減

周志華《機器學習》課後習題(第五章):神經網絡

 是一個超參。這裡代碼實作了Adam,下面

代碼和5.5一同實作,同樣在:

這裡隻嘗試了sklearn 中自帶的iris資料集試了一下。同樣學習率下,兩者訓練時損失函數如下:
周志華《機器學習》課後習題(第五章):神經網絡

可以明顯看出adam的速度更快的。

5.7 根據式 (5.18)和 (5.19) ,試構造一個能解決異或問題的單層 RBF 神經網絡。

這裡可以使用X = array([[1, 0], [0, 1], [0, 0], [1, 1]]),y = array([[1], [1], [0], [0]])作為資料,訓練一個RBF神經網絡。

這裡使用均方根誤差作為損失函數;輸出層和書上一緻,為隐藏層的線性組合,且另外加上了一個偏置項(這是書上沒有)。

'''
這裡使用均方根誤差作為損失函數的RBF神經網絡。
'''
import numpy as np
import matplotlib.pyplot as plt


def RBF_forward(X_, parameters_):
    m, n = X_.shape
    beta = parameters_['beta']
    W = parameters_['W']
    c = parameters_['c']
    b = parameters_['b']

    t_ = c.shape[0]
    p = np.zeros((m, t_))  # 中間隐藏層的激活值     對應書上5.19式
    x_c = np.zeros((m, t_))  # 5.19式中 x - c_{i}
    for i in range(t_):
        x_c[:, i] = np.linalg.norm(X_ - c[[i],], axis=1) ** 2

        p[:, i] = np.exp(-beta[0, i] * x_c[:, i])

    a = np.dot(p, W.T) + b
    return a, p, x_c


def RBF_backward(a_, y_, x_c, p_, parameters_):
    m, n = a_.shape
    grad = {}
    beta = parameters_['beta']
    W = parameters_['W']

    da = (a_ - y_)      # 損失函數對輸出層的偏導 ,這裡的a其實對應着  輸出層的y_hat

    dw = np.dot(da.T, p_) / m
    db = np.sum(da, axis=0, keepdims=True) / m
    dp = np.dot(da, W)   # dp即損失函數對隐藏層激活值的偏導

    dbeta = np.sum(dp * p_ * (-x_c), axis=0, keepdims=True) / m

    assert dbeta.shape == beta.shape
    assert dw.shape == W.shape
    grad['dw'] = dw
    grad['dbeta'] = dbeta
    grad['db'] = db

    return grad


def compute_cost(y_hat_, y_):
    m = y_.shape[0]
    loss = np.sum((y_hat_ - y) ** 2) / (2 * m)
    return np.squeeze(loss)


def RBF_model(X_, y_, learning_rate, num_epochs, t):
    '''
    :param X_:
    :param y_:
    :param learning_rate:  學習率
    :param num_epochs:     疊代次數
    :param t:   隐藏層節點數量
    :return:
    '''
    parameters = {}
    np.random.seed(16)
    # 定義中心點,本來這裡的中心點應該由随機采用或者聚類等非監督學習來獲得的,這裡為了簡單就直接定義好了

    parameters['beta'] = np.random.randn(1, t)  # 初始化徑向基的方差
    parameters['W'] = np.zeros((1, t))  # 初始化
    parameters['c'] = np.random.rand(t, 2)
    parameters['b'] = np.zeros([1, 1])
    costs = []

    for i in range(num_epochs):
        a, p, x_c = RBF_forward(X_, parameters)
        cost = compute_cost(a, y_)
        costs.append(cost)
        grad = RBF_backward(a, y_, x_c, p, parameters)

        parameters['beta'] -= learning_rate * grad['dbeta']
        parameters['W'] -= learning_rate * grad['dw']
        parameters['b'] -= learning_rate * grad['db']

    return parameters, costs


def predict(X_, parameters_):
    a, p, x_c = RBF_forward(X_, parameters_)

    return a


X = np.array([[1, 0], [0, 1], [0, 0], [1, 1]])
y = np.array([[1], [1], [0], [0]])
#

parameters, costs = RBF_model(X, y, 0.003, 10000, 8)

plt.plot(costs)
plt.show()

print(predict(X, parameters))

# 梯度檢驗
# parameters = {}
# parameters['beta'] = np.random.randn(1, 2)  # 初始化徑向基的方差
# parameters['W'] = np.random.randn(1, 2)  # 初始化
# parameters['c'] = np.array([[0.1, 0.1], [0.8, 0.8]])
# parameters['b'] = np.zeros([1, 1])
# a, p, x_c = RBF_forward(X, parameters)
#
# cost = compute_cost(a, y)
# grad = RBF_backward(a, y, x_c, p, parameters)
#
#
# parameters['b'][0, 0] += 1e-6
#
# a1, p1, x_c1 = RBF_forward(X, parameters)
# cost1 = compute_cost(a1, y)
# print(grad['db'])
#
# print((cost1 - cost) / 1e-6)      

最後輸出是:

[[ 9.99944968e-01]

[ 9.99881045e-01]

[ 8.72381056e-05]

[ 1.26478454e-04]]

感覺,分類的時候在輸出層使用sigmoid作為激活函數也可以。

5.8 從網上下載下傳或自己程式設計實作 SOM 網絡,并觀察其在西瓜資料集 3.0α上産生的結果。

花了挺長時間看,寫完代碼的發現結果和預期有點不太符合,先暫時放一下吧還是...代碼不完整就不放了。

這裡提一個迷惑了我很久的一點,有些部落格說SOM神經網絡的聚類類别不需要自己定義,其實是不對的,SOM神經網絡輸出聚類類别是需要自己定義,每個輸出節點對應着一個類别,通過計算樣本和輸出節點的權重向量的相似度來确定樣本屬于哪個類别(節點);輸入節點的數量和樣本的次元一樣(和BP網絡相同);輸出的節點常常是以二維矩陣(這裡可以是正方形也可以多邊形等)或者一維直線的形式,每一個輸出節點對應着一個權重向量和輸入節點實作全連接配接。

想了解SOM建議參考下面幾個連結:

https://www.jianshu.com/p/41fc86728928 https://github.com/KeKe-Li/tutorial/blob/master/assets/src/SOM/SOM.md https://www.cs.bham.ac.uk/~jxb/NN/l16.pdf

5.9* 試推導用于 Elman 網絡的 BP 算法.

Elman 網絡在西瓜書原書上說的是“遞歸神經網絡”,但是在網上找資料說的

“遞歸神經網絡”是空間次元的展開,是一個樹結構。

“循環神經網絡”是時間次元的展開,代表資訊在時間次元從前往後的的傳遞和積累。

從書上p111描述來看感覺更像“循環神經網絡”。最近時間不多(lan..),就不去啃原論文了。關于“循環神經網絡”或者遞歸神經網絡的BP可以參考下面連結。

1、零基礎入門深度學習(5) - 循環神經網絡 ,網上大神寫了。

https://zybuluo.com/hanbingtao/note/541458

另外關于循環神經網絡也可以看看吳恩達老師的深度學習課程“序列模型”那部分。

5.10 從網上下載下傳或自己程式設計實作一個卷積神經網絡并在手寫字元識别資料 MNIST 上進行實驗測試。

正好前段時間做過Kaggle上手寫數字識别的題目。這裡正好放上來,CNN是用Tensorflow實作的,之前看吳恩達老師深度學習課程的時候也拿numpy實作過(課程作業),等以後有時間再整理放上來吧。

https://github.com/han1057578619/kaggle_competition/tree/master/Digit_Recogniz

系列文章:

1. 

周志華機器學習課後習題解析【第二章】

2. 

周志華《機器學習》課後習題(第三章):線性模型

3. 

周志華《機器學習》課後習題解析(第四章):決策樹