吳恩達機器學習課程-作業4-神經網絡訓練(python實現)

Machine Learning(Andrew) ex4-Neural Networks Learning

椰汁筆記

Neural Networks

  • 1.1 Visualizing the data
  • 1.2 Model representation

由於本次作業的數據和網絡結構和上次的作業相同,因此這兩步已經在上次作業中完成,這裏不再贅述。

  • 1.3 Feedforward and cost function

在這裏插入圖片描述
當前網絡的前向傳播計算方法
a(1)=xa(1)a0(1)=1z(2)=Θ(1)a(1)a(2)=g(z(2))a(2)a0(2)=1z(3)=Θ(2)a(2)a(3)=g(z(3))=hθ(x) a^{(1)}=x\\ 向a^{(1)}添加偏執單元a_0^{(1)}=1\\ z^{(2)}=\Theta^{(1)}a^{(1)}\\ a^{(2)}=g(z^{(2)})\\ 向a^{(2)}添加偏執單元a_0^{(2)}=1\\ z^{(3)}=\Theta^{(2)}a^{(2)}\\ a^{(3)}=g(z^{(3)})=h_\theta(x)
最後利用輸出層計算cost
J(θ)=1mi=1mk=1K[yk(i)log((hθ(x(i)))k)(1yk(i))log(1(hθ(x(i)))k)] J(\theta)=\frac{1}{m}\sum_{i=1}^m\sum_{k=1}^K[-y^{(i)}_klog((h_{\theta}(x^{(i)}))_k)-(1-y^{(i)}_k)log(1-(h_{\theta}(x^{(i)}))_k)]

這裏的y需要注意代表的意義

數字 1 2 3 4 5 6 7 8 9 0
對應的向量下標 0 1 2 3 4 5 6 7 8 9
    a1 = X  # (5000,400)
    a1 = np.insert(a1, 0, 1, axis=1)  # (5000,401)
    z2 = a1.dot(theta1.T)  # (5000,25)
    a2 = sigmoid(z2)  # (5000,25)
    a2 = np.insert(a2, 0, 1, axis=1)  # (5000,26)
    z3 = a2.dot(theta2.T)  # (5000,10)
    a3 = sigmoid(z3)  # (5000,10)
    cost = np.mean(np.sum((-y) * np.log(a3) - (1 - y) * np.log(1 - a3), axis=1))
    print(cost)#0.2876291651613189

在封裝裝成函數時,發現這個其實可以寫成一個循環去處理每層。
在傳入theta時需要考慮到後面使用高級的優化方法時theta只能是一維向量,所有我們需要在傳入多個theta時先展開成一個向量,在內部再恢復

def serialize(thetas):
    """
    將多個參數多維數組,映射到一個向量上
    :param thetas: tuple or list,按順序存儲每層的theta參數,每個爲ndarray
    :return: ndarray,一維化的參數向量
    """
    res = np.array([0])
    for t in thetas:
        res = np.concatenate((res, t.ravel()), axis=0)
    return res[1:]


def deserialize(theta):
    """
    將向量還原爲多個參數(只適用當前網絡)
    :param theta: ndarray,一維化的參數向量
    :return: tuple ,按順序存儲每層的theta參數,每個爲ndarray
    """
    return theta[:25 * 401].reshape(25, 401), theta[25 * 401:].reshape(10, 26)

直接使用循環進行計算,每層計算的操作都是先添加偏置單元,再計算z,再計算A。

def not_regularized_cost(thetas, X, y):
    """
    計算非正則化的損失值
    :param theta: ndarray,一維參數向量
    :param X: ndarray,輸入層的輸入值
    :param y: ndarray,數據的標記
    :return: float,損失值
    """
    for t in deserialize(thetas):
        X = np.insert(X, 0, 1, axis=1)
        X = X.dot(t.T)
        X = sigmoid(X)
    return np.mean(np.sum((-y) * np.log(X) - (1 - y) * np.log(1 - X), axis=1))

從邏輯迴歸那裏我們知道,由於特徵太多,可能會出現過擬合的現象,需要正則化來解決
J(θ)=1mi=1mk=1K[yk(i)log((hθ(x(i)))k)(1yk(i))log(1(hθ(x(i)))k)]+λ2m[j=025k=1400(Θj,k(1))2+j=025k=1400(Θj,k(2))2] J(\theta)=\frac{1}{m}\sum_{i=1}^m\sum_{k=1}^K[-y^{(i)}_klog((h_{\theta}(x^{(i)}))_k)-(1-y^{(i)}_k)log(1-(h_{\theta}(x^{(i)}))_k)]\\ +\frac{\lambda}{2m}[\sum_{j=0}^{25}\sum_{k=1}^{400}(\Theta_{j,k}^{(1)})^2+\sum_{j=0}^{25}\sum_{k=1}^{400}(\Theta_{j,k}^{(2)})^2]
直接在之前的損失函數計算中,加入懲罰項即可。注意不要懲罰偏執單元。

def regularized_cost(theta, X, y, l):
    """
    計算正則化的損失值
    :param theta: ndarray,一維參數向量
    :param X: ndarray,輸入層的輸入值
    :param y: ndarray,數據的標記
    :param l: float,懲罰參數
    :return: float,損失值
    """
    m = X.shape[0]
    part2 = 0.0
    for t in deserialize(theta):
        X = np.insert(X, 0, 1, axis=1)
        X = X.dot(t.T)
        X = sigmoid(X)
        t = t[..., 1:]  # 要去掉bias unit
        part2 += (l / (2 * m)) * np.sum(t * t)
    part1 = np.mean(np.sum((-y) * np.log(X) - (1 - y) * np.log(1 - X), axis=1))
    return part1 + part2
print(regularized_cost(theta, X, y, 1))#0.38376985909092365

Backpropagation

反向傳播算法爲梯度下降的計算提供了一個方法。這裏我們先不考慮起原理,直接動手實現。
反向傳播算法,細節舉例(這裏以課程中的4層網絡舉例)
δj(l)ljδ(4)=a(4)yδ(3)=(Θ(3))Tδ(4)g(z(3))δ(2)=(Θ(2))Tδ(3)g(z(2)) 引入{\delta_j^{(l)}},表示第l層的單元j的“損失”\\ \delta^{(4)}=a^{(4)}-y\\ \delta^{(3)}=(\Theta^{(3)})^T\delta^{(4)}\cdot g'(z^{(3)})\\ \delta^{(2)}=(\Theta^{(2)})^T\delta^{(3)}\cdot g'(z^{(2)})
這裏沒有d1要注意!!!
完整的反向傳播算法
{(x(1),y(1)),(x(2),y(2)),,(x(m),y(m))}Δij(l)=0(for all i,j)For i=1 to m: a(1)=x(i)使a(l)δ(l)Δij(l)=Δij(l)+aj(l)δi(l+1),(Δ(l)=Δ(l)+δ(l+1)(a(l))T)Dij(l)=1mΔij(l)+λΘij(l),(if j0)Dij(l)=1mΔij(l),(if j=0)J(Θ)θij(l)=Dij(l) 訓練集\{(x^{(1)},y^{(1)}),(x^{(2)},y^{(2)}),\dots,(x^{(m)},y^{(m)})\} \\令\Delta_{ij}^{(l)}=0(for\ all\ i,j) \\For\ i=1\ to\ m: \\令\ a^{(1)}=x^{(i)} \\使用前向傳播算法計算出所有a^{(l)} \\計算所有\delta^{(l)} \\計算\Delta_{ij}^{(l)}=\Delta_{ij}^{(l)}+a_j^{(l)}\delta_i^{(l+1)},(\Delta^{(l)}=\Delta^{(l)}+\delta^{(l+1)}(a^{(l)})^T) \\D_{ij}^{(l)}=\frac{1}{m}\Delta_{ij}^{(l)}+\lambda\Theta_{ij}^{(l)},(if\ j\ne0) \\D_{ij}^{(l)}=\frac{1}{m}\Delta_{ij}^{(l)},(if\ j=0) \\\frac{\partial J(\Theta)}{\partial \theta_{ij}^{(l)}}=D_{ij}^{(l)}

  • 2.1 Sigmoid gradient

在計算d時需要用到sigmoid函數的導數,這裏很簡單可以自己推到一下,這裏我們實現
g(z)=ddzg(z)=g(z)(1g(z)) g'(z)=\frac{d}{dz}g(z)=g(z)(1-g(z))

def sigmoid_gradient(z):
    return sigmoid(z) * (1 - sigmoid(z))
print(sigmoid_gradient(0))#0.25
  • 2.2 Random initialization

神經網絡的參數初始化是非常講究的,不能像以前一樣全部初始化爲0,這樣會導致所有隱藏單元計算的值相同高度冗餘,這裏選擇將參數隨機化到一個[-e,e]的範圍內,這裏的e選擇方法
e=6Lin+Lout e=\frac{\sqrt{6}}{\sqrt{L_{in}+L_{out}}}

def random_initialize_weights(shape, e=0.12):
    """
    隨機初始化參數,範圍爲[-e, e]
    :param shape: tuple or list,需要初始化的參數的規格
    :param e: float,邊界
    :return: ndarray,參數矩陣
    """
    return (np.random.rand(shape[0], shape[1]) - 0.5) * 2 * e
  • 2.3 Backpropagation

直接按照反向傳播算法做就好

def back(theta, X, y, l):
    """
    反向傳播算法
    :param theta: ndarray,一維參數向量
    :param X: ndarray,輸入層的輸入值
    :param y: ndarray,數據的標籤
    :param l: float,懲罰參數
    :return: ndarray,下降後一維參數向量
    """
    A, Z = feedforward(theta, X)
    a1, a2, a3 = A  # a1(5000,401), a2(5000,26), a3(5000,10)
    z2, z3 = Z  # z2(5000,25), z3(5000,10)
    theta1, theta2 = deserialize(theta)  # theta1(25,401), theta2(10,26)
    m = X.shape[0]

    d3 = a3 - y  # d3(5000,10)
    d2 = d3.dot(theta2)[..., 1:] * sigmoid_gradient(z2)  # d2(5000,25)

    theta1 = np.insert(np.delete(theta1, 0, axis=1), 0, 0, axis=1)
    theta2 = np.insert(np.delete(theta2, 0, axis=1), 0, 0, axis=1)
    D1 = (1 / m) * d2.T.dot(a1) + (l / m) * theta1  # D1(25,401)
    D2 = (1 / m) * d3.T.dot(a2) + (l / m) * theta2  # D2(10,26)
    return serialize((D1, D2))
  • 2.4 Gradient checking
    爲了方便檢測我們的梯度下降算法是否正確,我們採用近似計算的方式來對比
    let θ(i+)=θ+[00ϵ0],θ(i)=θ[00ϵ0]fi(θ)J(θ(i+))J(θ(i))2ϵ let\ \theta^{(i+)}=\theta+\left[\begin{array}{ccc} 0 \\\\ 0 \\\\ \vdots \\\\ \epsilon \\\\ \vdots \\\\ 0 \\\\\end{array}\right],\theta^{(i-)}=\theta-\left[\begin{array}{ccc} 0 \\\\ 0 \\\\ \vdots \\\\ \epsilon \\\\ \vdots \\\\ 0 \\\\\end{array}\right] \\f_i(\theta)\approx\frac{J(\theta^{(i+)})-J(\theta^{(i-)})}{2\epsilon}
def gradient_checking(theta, X, y, l, e=10 ** -4):
    """
    檢測反向傳播算法是否正確運行
    :param theta: ndarray,一維參數向量
    :param X: ndarray,輸入層的輸入值
    :param y: ndarray,數據的標籤
    :param l: float,懲罰參數
    :param e: float,微小擾動
    :return: ndarray,下降後一維參數向量
    """
    res = np.zeros(theta.shape)
    for i in range(len(theta)):
        left = np.array(theta)
        left[i] -= e
        right = np.array(theta)
        right[i] += e
        gradient = (regularized_cost(right, X, y, l) - regularized_cost(left, X, y, l)) / (2 * e)
        res[i] = gradient
    return res

這個計算是非常耗時的,應用到一部分計算中對比結果,檢測反向傳播算法是否正確。之後要拿出。

  • 2.5 Regularized Neural Networks

在上面我就一併做了正則化

  • 2.6 Learning parameters using fmincg

下面開始訓練我們的模型,這裏建議訓練完後將參數保存一下,因爲訓練時間還是比較久,免得後面重跑。

    # 以上是爲了測試寫得是否正確,所以按照提供數據更改了y,下面我們將使用最自然的y表示方式
    y = convert(data['y'])
    theta1 = random_initialize_weights((25, 401))
    theta2 = random_initialize_weights((10, 26))
    theta = serialize((theta1, theta2))
    # 參數初始化得不同,有時會導致溢出
    res = opt.minimize(fun=regularized_cost, x0=theta, args=(X, y, 1), method="TNC", jac=back)
    print(res)
    theta1, theta2 = deserialize(res.x)
    sio.savemat("parametersWeights.mat", {"theta1": theta1, "theta2": theta2})

接着評價一下結果

def predict(theta, X):
    a3 = feedforward(theta, X)[0][-1]
    p = np.zeros((1, 10))
    for i in a3:
        index = np.argmax(i)
        temp = np.zeros((1, 10))
        temp[0][index] = 1
        p = np.concatenate((p, temp), axis=0)
    return p[1:]
print(classification_report(y, predict(res.x, X)))
visualizing_the_hidden_layer(res.x, X)

可以看到,神經網絡相較於邏輯迴歸擬合得更好,但是計算量確實大,我們這裏只有一個隱藏層久需要計算數分鐘,很難想象大型網絡得計算需要多大得計算資源。當然我們這裏得神經網絡是最簡單得全連接神經網絡,參數確實多。神經網絡還有其他帶有非全連接得網絡結構,這些結構會使參數量下降。
在這裏插入圖片描述

Visualizing the hidden layer

最後我麼可以看看隱藏層輸出得圖像是個什麼樣子
注意在計算後顯示要去掉偏執單元

def visualizing_the_hidden_layer(theta, X):
    """
    可視化顯示隱藏層的輸入和輸出
    :param theta: ndarray,一維參數向量
    :param X: ndarray,輸入層的輸入值
    :return: None
    """
    A, _ = feedforward(theta, X)
    a1, a2, a3 = A
    # 要去掉bias unit
    input = a1[..., 1:][:25]
    output = a2[..., 1:][:25]
    input = mapping(input, 5)
    output = mapping(output, 5)
    plt.subplot(1, 2, 1)
    plt.axis('off')
    plt.imshow(input.T)
    plt.title("hidden layer input")

    plt.subplot(1, 2, 2)
    plt.axis('off')
    plt.imshow(output)
    plt.title("hidden layer output")
    plt.show()

只觀察0得數據,可以看到,輸出後得圖像是存在一些相似。
在這裏插入圖片描述
總結一下簡單的神經網絡使用方法

  • 1.選擇一個網絡結構
    • 將當前得特徵數量作爲輸入層得單元數量
    • 將分類得類數作爲輸出單元數量
    • 當隱藏層大於1層時,通常所有隱藏層得單元數一樣
  • 2.訓練網絡
    • 隨機初始化單元連接之間得權重
    • 實現前向傳播
    • 實現計算損失值
    • 實現反向傳播
    • 實現梯度下降
    • 最小化損失值
  • 3.評價模型
    • 在訓練網絡前,需要將數據隨機分爲三部分,作爲訓練集、交叉驗證集和測試集
    • 使用訓練集訓練網絡
    • 交叉驗證集用於,當需要從多個網絡模型中擇優時,利用交叉驗證集計算誤差,通過量化的方式選擇,模型
    • 測試集用於最終評價模型

完整的代碼會同步在我的github

歡迎指正錯誤

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