1. 程式人生 > >Python神經網路程式設計筆記

Python神經網路程式設計筆記

神經元

想一想便知道,當一個人捏你一下以至於你會痛得叫起來的力度便是神經元的閾值,而我們構建的時候也是把這種現象抽象成一個函式,叫作啟用函式。

而這裡便是我們使用sigmoid函式的原因,它是一個很簡單的函式,平滑更接近顯示。

​ \[y=\frac{1}{1+e^{-x}}\]

神經網路傳遞訊號

神經網路便是通過一個一個神經元連線,使用權值x輸入的和在通過sigmoid函式得到最終的輸出值,然後一層一層的傳遞下去。

\[O = sigmoid(W\cdot I)\]

其中,\(O\)為輸出矩陣,\(W\)為權值矩陣,\(I\)為輸入矩陣。

舉個栗子:

假設我們設定一個三層神經網路,分別為輸入層,隱藏層(注意:不管我們中間有多少層,中間的都叫隱藏層,我們這裡隱藏層只有一層),輸出層。

1.輸入層->隱藏層:我們輸入矩陣是一個(3x1)的矩陣,那麼我們設定四個權值,那麼我們的第一個權值矩陣(就是輸入層->隱藏層的)的維度也就為(4x3),這時我們相乘也就得到輸出矩陣(4x1),進行下一步時,這個(4x1)的輸出矩陣就變成了輸入;

2.隱藏層->輸出層:這是我們的輸入矩陣是一個(4x1)的矩陣,然後我們要求輸出層輸出為兩個值,那麼我們第二個權值矩陣(就是隱藏層->輸出層的)的維度也就為(2x4),這時我們相乘也就得到輸出矩陣(2x1),也就為最終的結果了。

反向傳播

現在我們已經可以收到由前面的層傳輸過來的結果了,但答案肯定是不準的,那麼我們該如何進行改進呢?

毫無疑問,首先我們要計算誤差,假設真實值為t,輸出值為o,那麼誤差e就為:

\[e=t-o\]

我們按照上圖來進行舉例說明。

1.更新誤差

那麼,隱藏層的誤差如何確定呢?

我們使用連結\(w_{1,1}\)和連結\(w_{2,1}\)上的分割誤差之和來進行更新,也就是

\[e_{hidden,1}=e_{output,1}*\frac{w_{1,1}}{w_{1,1}+w_{2,1}}+e_{output,2}*\frac{w_{1,2}}{w_{1,2}+w_{2,2}}\]

我們也進行帶值進行計算

\[0.8*\frac{2}{2+3}+0.5*\frac{1}{1+4}=0.42\]

2.使用矩陣進行更新

我們發現上面的公式應用到矩陣運算會很複雜,我們究其本質,最重要的事情是輸出誤差與連結權重\(w_{ij}\)的乘法。較大的權重就意味著攜帶較多的輸出誤差給隱藏層,這些分數的分母是一種歸一化因子。如果我們忽略這種因子,那麼我們僅僅失去後潰誤差的大小。

也就是這裡我們使用\(e_1*w_{1,1}\)來代替\(e_1*w_{1,1}/(w_{1,1}+w_{2,1})\) 。那麼我們就可以很容易的進行矩陣運算進行誤差更新了。

\[error_{hidden}=w^T_{hidden\_output}\cdot error_{output}\]

3.更新權重

在神經網路中,我們採用梯度下降法來尋找最優的權重值。神經網路本身的輸出函式部署一個誤差函式,但我們知道,由於誤差是目標訓練值與實際輸出值之間的差值,因此我們可以很容易的構建誤差函式,即

\[(目標值-實際值)^2\]

為什麼我們要構建平方項呢?為何不用絕對值誤差呢?原因有三

  1. 使用誤差的平方,我們可以很容易的使用代數計算出梯度下降的斜率;
  2. 誤差函式平滑連續,這是的梯度下降法很好地發揮作用,沒有間斷,也沒有突然的跳躍;
  3. 越接近最小值,梯度越小,這意味著,如果我們使用這個函式調節步長,超調的風險就會變得很小。

現在我們要更新\(w_{j,k}\)的權值,那麼來推導一下它的更新公式:

首先有

\[\frac{\partial E}{\partial W_{j,k}} = \frac{\partial}{\partial W_{j,k}}(t_k-o_k)^2\]

然後根據鏈式法則得到:

\[\frac{\partial E}{\partial W_{j,k}} = \frac{\partial E}{\partial o_k} \cdot \frac{\partial o_k}{\partial W_{j,k}}\]

然後我們對其求偏導:

\[\frac{\partial E}{\partial W_{j,k}} = -2(t_k-o_k) \cdot \frac{\partial o_k}{\partial W_{j,k}}\\ = -2(t_k-o_k) \cdot \frac{\partial}{\partial W_{j,k}}sigmoid(\sum_{j}w_{j,k}\cdot o_j)\\ = -2(t_k-o_k) \cdot sigmoid(\sum_{j}w_{j,k}\cdot o_j)(1-sigmoid(\sum_{j}w_{j,k}\cdot o_j)) \cdot \frac{\partial}{\partial W_{j,k}}(\sum_{j}w_{j,k}\cdot o_j)\\ = -2(t_k-o_k) \cdot sigmoid(\sum_{j}w_{j,k}\cdot o_j)(1-sigmoid(\sum_{j}w_{j,k}\cdot o_j)) \cdot o_j\]

這樣我們就得到了最後的權重更新公式:

\[new W_{j,k} = oldW_{j,k} - \alpha \cdot \frac{\partial E}{\partial W_{j,k}} \]

其中:

\[\frac{\partial E}{\partial W_{j,k}} = \Delta w_{j,k} = \alpha \cdot E_k \cdot O_k(1-O_k) \cdot O_j^T\]

輸入與輸出

1.輸入

我們觀察sigmoid函式注意到,當輸入值變大,啟用函式也就會越來越平坦,權重的改變取決於啟用函式的梯度,小梯度也就意味著限制了神經網路的學習能力,這就是所謂的飽和神經網路。因此,我們要儘量保持小的輸入。

但有趣的是,當輸入訊號太小,計算機便會損失精度,所以我們要保持輸入範圍在0.0~1.0之間,但輸入為0的話會將\(o_j\)設定為0,這樣的權重更新表示式就會等於0,從而造成學習能力的喪失,我們需要加上一個小小的偏移,例如0.01,避免輸入0帶來的麻煩。

2.輸出

我們使用啟用函式得到的值的範圍會被限制在0~1之間,注意:邏輯函式甚至不能取到1.0,只能接近於1.0.數學家們稱之為漸進於1.0.

因此,我們需要調整目標值,匹配啟用函式的可能輸出,常見的使用範圍為0.0~1.0之間,但我們是取不到0.0和1.0的,所以這裡我們也要進行偏移,例如0.01~0.99.

隨機初始權重

和輸入輸出一樣,初始的權重設定也要遵從同樣地原則。過大的初始權重會造成大的訊號傳遞給啟用函式,導致網路飽和,從而降低學習到更好的權重的能力,因此應該避免大的初始權重值。

我們可以從-1.0~+1.0之間隨機均勻地挑選初始權重。而我們也希望初始權重的分佈是均勻的,經過數學家們的證明,我們有一個比較好的挑選方式,那就是從均值為0、標準方差等於節點傳入連結數量平方根倒數的正態分佈中進行取樣。

總而言之,我們要禁止將初始權重設定為0或者將初始權重設定為像痛得恆定值,這樣會很糟糕。

程式碼實現

import numpy as np
import scipy.special
import matplotlib.pyplot as plt


# neural network class definition
class NeuralNetwork:
    def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
        # set number of nodes in each input, hidden, output layer
        self.inodes = inputnodes
        self.hnodes = hiddennodes
        self.onodes = outputnodes

        # learning rate
        self.lr = learningrate

        # 初始權重矩陣
        self.wih = np.random.normal(0.0, pow(self.hnodes, -0.5), (self.hnodes, self.inodes))
        self.who = np.random.normal(0.0, pow(self.onodes, -0.5), (self.onodes, self.hnodes))

        # 啟用函式
        self.activation_function = lambda x: scipy.special.expit(x)
        pass

    def train(self, inputs_list, targets_list):
        # 輸入
        inputs = np.array(inputs_list, ndmin=2).T
        targets = np.array(targets_list, ndmin=2).T
        # 隱藏層計算
        hidden_inputs = np.dot(self.wih, inputs)
        hidden_outputs = self.activation_function(hidden_inputs)
        # 輸出層計算
        final_inputs = np.dot(self.who, hidden_outputs)
        final_outputs = self.activation_function(final_inputs)
        # 誤差計算
        output_errors = targets - final_outputs
        hidden_errors = np.dot(self.who.T, output_errors)
        # 反向傳播更新權值
        self.who += self.lr * np.dot((output_errors * final_outputs * (1.0 - final_outputs)), np.transpose(hidden_outputs))
        self.wih += self.lr * np.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)),  np.transpose(inputs))
        pass

    def query(self, inputs_list):
        # 輸入
        inputs = np.array(inputs_list, ndmin=2).T
        # 隱藏層計算
        hidden_inputs = np.dot(self.wih, inputs)
        hidden_outputs = self.activation_function(hidden_inputs)
        # 輸出層計算
        final_inputs = np.dot(self.who, hidden_outputs)
        final_outputs = self.activation_function(final_inputs)
        return final_outputs


if __name__ == '__main__':
    input_nodes = 784
    hidden_nodes = 200
    output_nodes = 10

    learning_rate = 0.2

    nn = NeuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate)

    # 載入資料
    train_data_file = open("./mnist_train.csv", "r")
    train_data_list = train_data_file.readlines()
    train_data_file.close()
    print("資料讀取完畢")

    # 視覺化
    # all_values = train_data_list[0].split(',')
    # image_array = np.asfarray(all_values[1:]).reshape((28, 28))
    # plt.imshow(image_array, cmap="Greys", interpolation='None')
    # plt.show()

    epochs = 2

    for e in range(epochs):
        print("\t===== epochs %d =====\t" % (e+1))
        for record in train_data_list:
            all_values = record.split(',')
            inputs = (np.asfarray(all_values[1:]) / 255 * 0.99) + 0.01

            targets = np.zeros(output_nodes) + 0.01
            targets[int(all_values[0])] = 0.99

            nn.train(inputs, targets)

    # 預測
    test_data_file = open("./mnist_test.csv", "r")
    test_data_list = test_data_file.readlines()
    test_data_file.close()
    print(len(test_data_list))

    t_num = 0
    for line in test_data_list:
        all_values = line.split(',')
        y = all_values[0]
        y_pred = np.argmax(nn.query(np.asfarray(all_values[1:]) / 255 * 0.99 + 0.01))
        if int(y) == int(y_pred):
            t_num += 1

    print(t_num)
    print(t_num * 1.0 / len(test_data_list))

這份三層神經網路對mnist手寫資料集能達到97%的準確度