深度學習與計算機視覺:基於Python的神經網絡的實現


在前面兩篇文章介紹了深度學習的一些基本概念,本文則使用Python實現一個簡單的深度神經網絡,並使用MNIST數據庫進行測試。
神經網絡的實現,包括以下內容:

  • 神經網絡權值的初始化
  • 正向傳播
  • 誤差評估
  • 反向傳播
  • 更新權值

主要是根據反向傳播的4個基本方程,利用Python實現神經網絡的反向傳播。

初始化

首先定義代表神經網絡的類NeuralNetwork,

class NeuralNetwork:
    def __init__(self,layers,alpha=0.1):
        self.W = []
        self.layers = layers
        self.alpha = alpha

有三個屬性,

  • W存儲各個層之間的權值矩陣,也是神經網絡要更新學習的
  • layers 神經網絡的結構,例如: [2,2,1]表示輸入層有2個神經元,隱藏層2個神經元,輸出層只有1個神經元。
  • alpha 學習速率

接下來初始化各個層之間的權值矩陣

for i in np.arange(0,len(layers) - 2):
    w = np.random.randn(layers[i] + 1,layers[i + 1] + 1)
    self.W.append(w / np.sqrt(layers[i]))

注意上面生成權值矩陣的大小layers[i] + 1,layers[i + 1] + 1,都加了1。 這是將神經元的偏置和權值統一的放到了權值矩陣里面。

\[\left[ \begin{array}{c}w_{11} & w_{12} \\ w_{21} & w_{22}\end{array} \right] \cdot \left[\begin{array}{c}x_1 \\ x_2\end{array}\right] + \left[\begin{array}{c}b_1 \\ b_2\end{array}\right] = \left[\begin{array}{c}w_{11}x_1 + w{12}x_2 + b_1 \\ w_{21}x_1 + w_{22}x_2 + b_2 \end{array}\right] \]

可以將上式寫成齊次的形式

\[\left[ \begin{array}{c}w_{11} & w_{12} & b_1 \\ w_{21} & w_{22} &b_2 \end{array} \right] \cdot \left[\begin{array}{c}x_1 \\ x_2 \\ 1\end{array}\right] \]

使用統一的矩陣運算,在正向反向傳播的時候更方便。

在輸出層的神經元並沒有偏置,所以要單獨初始化輸出層的權值矩陣

        w = np.random.randn(layers[-2] + 1,layers[-1])
        self.W.append(w / np.sqrt(layers[-2]))

下面實現Python的magic function __repr__輸出神經網絡結構

    def __repr__(self):
        return "NeuralNetWork:{}".format("-".join(str(l) for l in self.layers))

激活函數

在神經網絡中使用sigmoid作為激活函數,實現sigmoid及其導數

    def sigmoid(self,x):
        return 1.0 / (1 + np.exp(-x))

    def sigmoid_deriv(self,x):
        return x * (1 - x)

正向反向傳播

這一部分是神經的網絡的核心了。下面實現fit方法,在方法中完成神經網絡權值更新(訓練)的過程。

    def fit(self,X,y,epochs=1000,displayUpdate=100):
        X = np.c_[X,np.ones((X.shape[0]))]

        for epoch in np.arange(0,epochs):
            for(x,target) in zip(X,y):
                self.fit_partial(x,target)

            # check to see if we should display a training update
            if epoch == 0 or (epoch + 1) % displayUpdate == 0:
                loss = self.calculate_loss(X,y)
                print("[INFO] epoch={},loss={:.7f}".format(epoch + 1,loss))

該函數有4個參數:

  • X是輸入的樣本數據
  • y是樣本的真是值
  • epochs訓練的輪數
  • displayUpdate 輸出訓練的loss值。

X = np.c_[X,np.ones((X.shape[0]))]將輸入訓練的樣本表示為齊次向量(也就是在末尾添1)。fit_partial是對輸入的每個樣本進行訓練,包括正向傳播,反向傳播以及權值的更新。

    def fit_partial(self,x,y):
        A = [np.atleast_2d(x)]
        # 正向傳播
        # 層層之間的數據傳遞
        for layer in np.arange(0,len(self.W)):
        
            # 輸入經過加權以及偏置后的值
            net = A[layer].dot(self.W[layer])

            # 神經元的輸出
            out = self.sigmoid(net)

            # 保存下來,反向傳播的時候使用
            A.append(out)

上面完成了神經玩過的正向傳播過程,下面根據反向傳播的4個基本方程進行反向傳播。
首先根據\(BP1\),

\[\delta^L = \frac{\partial e}{\partial a^L} \odot \sigma'(z^L) \tag{BP1} \]

計算輸出層的誤差\(\delta^L\)

        error = A[-1] - y # 輸出層的誤差,均值方差作為損失函數

        D = [error * self.sigmoid_deriv(A[-1])]

得到輸出層的誤差D后,根據\(BP2\)計算各個層的誤差

\[\delta^{L-1} = (W^L)^T\delta^L \odot \sigma'(z^{L-1}) \tag{BP2} \]

        for layer in np.arange(len(A) - 2,0 ,-1):
            delta = D[-1].dot(self.W[layer].T)
            delta = delta * self.sigmoid_deriv(A[layer])
            D.append(delta)
        D = D[::-1]

D反轉,和各個層的索引對應起來,下面根據\(BP3,BP4\)計算權值矩陣和偏置的導數

\[\frac{\partial e}{b_j^l} = \delta_j^l \tag{BP3} \]

\[\frac{\partial e}{w_{jk}^l} = \delta_j^l a_k^{l-1} \tag{BP4} \]

        for layer in np.arange(0,len(self.W)):
            self.W[layer] += -self.alpha * A[layer].T.dot(D[layer])

首先求得權值和偏置的導數(權值和偏置統一到同一個矩陣中)A[layer].T.dot(D[layer],然后將梯度乘以學習速率alpha 每次權值減小的步長。

上述就完成利用反向傳播算法更新權值的過程。 關於反向傳播四個基本方程的推導過程,可以參考文章深度學習與計算機視覺: 搞懂反向傳播算法的四個基本方程

誤差評估

上面代碼已經實現了深度學習的訓練過程,下面實現predict輸出使用訓練好的模型預測的結果,calculate_loss評估訓練后模型的評估

    def predict(self,X,addBias=True):
        p = np.atleast_2d(X)

        if addBias:
            p = np.c_[p,np.ones((p.shape[0]))]

        for layer in np.arange(0,len(self.W)):
                p = self.sigmoid(np.dot(p,self.W[layer]))

        return p 
    def calculate_loss(self,X,targets):

        targets = np.atleast_2d(targets)
        predictions = self.predict(X,addBias=False)
        loss = 0.5 * np.sum((predictions - targets) ** 2)

        return loss 

MNIST分類識別

使用上面實現的深度神經網絡對MNIST手寫體進行識別,首先導入必要的包

import NeuralNetwork
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn import datasets

需要使用sklearn包中的一些工具,進行數據的處理。

# load MNIST數據集,並使用min/max對數據進行歸一化
digits = datasets.load_digits()
data = digits.data.astype("float")
data = (data - data.min()) / (data.max() - data.min())

print("[INFO] samples: {}, dim: {}".format(data.shape[0], data.shape[1]))

將數據拆分為訓練集和測試集,並對MNIST的類別進行編碼

(trainX, testX, trainY, testY) = train_test_split(data, digits.target, test_size=0.25)

# convert the labels from integers to vectors
trainY = LabelBinarizer().fit_transform(trainY)
testY = LabelBinarizer().fit_transform(testY)

下面構建神經網絡結構,並使用訓練集進行訓練

nn = NeuralNetwork([data.shape[1], 32,16, 10])

print ("[INFO] {}".format(nn))

nn.fit(trainX, trainY, epochs=1000)

神經網絡結構為:64-32-16-10,其中64為輸入數據的大小,10輸出類別的個數。

最后評估訓練得到的模型

predictions = nn.predict(testX)

print(classification_report(testY.argmax(axis=1), predictions.argmax(axis=1)))

最終的輸出結果:

[INFO] loading MNIST (sample) dataset...
[INFO] samples: 1797, dim: 64
[INFO] training network...
[INFO] NeuralNetWork:64-32-16-10
[INFO] epoch=1,loss=607.1711647
[INFO] epoch=100,loss=7.1082795
[INFO] epoch=200,loss=4.0731690
[INFO] epoch=300,loss=3.1401868
[INFO] epoch=400,loss=2.8801101
[INFO] epoch=500,loss=1.8738122
[INFO] epoch=600,loss=1.7461474
[INFO] epoch=700,loss=1.6624043
[INFO] epoch=800,loss=1.1852884
[INFO] epoch=900,loss=0.6710255
[INFO] epoch=1000,loss=0.6336826
[INFO] evaluating network...
              precision    recall  f1-score   support

           0       1.00      0.95      0.97        39
           1       0.84      1.00      0.92        38
           2       1.00      0.98      0.99        41
           3       0.93      0.98      0.95        52
           4       0.91      0.97      0.94        40
           5       0.98      0.98      0.98        41
           6       1.00      0.96      0.98        51
           7       1.00      0.98      0.99        48
           8       0.98      0.89      0.93        55
           9       0.98      0.93      0.95        45

   micro avg       0.96      0.96      0.96       450
   macro avg       0.96      0.96      0.96       450
weighted avg       0.96      0.96      0.96       450

如上測試結果,在測試集的上表現還算不錯。

總結

本文使用Python簡單的實現了一個神經網絡。 主要是利用反向傳播的4個基本方程,實現反向傳播算法,更新各個神經元的權值。 最后使用該網絡,對MNIST數據進行識別分類。

上面實現的神經網絡只是“玩具”,用以加深對深度學習的訓練過程以及反向傳播算法的理解。后面將使用Keras和PyTorch來構建神經網絡。

本文代碼在git庫 https://github.com/brookicv/machineLearningSample


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM