深度神經網絡原理與實踐

理論基礎

什麼是神經網絡

咱們知道深度學習是機器學習的一個分支,是一種以人工神經網絡爲架構,對數據進行表徵學習的算法。而深度神經網絡又是深度學習的一個分支,它在 wikipedia 上的解釋以下:html

深度神經網絡(Deep Neural Networks, DNN)是一種判別模型,具有至少一個隱層的神經網絡,可使用反向傳播算法進行訓練。權重更新可使用下式進行隨機梯度降低法求解。

首先咱們能夠知道,深度神經網絡是一種判別模型。意思就是已知變量 x ,經過判別模型能夠推算出 y。好比機器學習中經常使用到的案例,經過手寫數字,模型推斷出手寫的是數字幾。node

image

深度神經網絡中的「深度」指的是一系列連續的表示層,數據模型中包含了多少層,這就被稱爲模型的「深度」。經過這些層咱們能夠對數據進行高層的抽象。以下圖所示,深度神級網絡由一個輸入層,多個(至少一個)隱層,以及一個輸出層構成,並且輸入層與輸出層的數量不必定是對等的。每一層都有若干個神經元,神經元之間有鏈接權重。python

image

仍是上面的案例,識別手寫數字,手寫的數字要怎麼轉成輸入呢?既然是手寫,那麼確定是一張圖片,圖片由多個像素點組成,這些像素點能夠構成一個輸入,通過多層神經網絡,輸出10個數字,這個10個數字就表明了數字 0 ~ 9 的機率。linux

image

神經元如何輸入輸出

神經網絡中的每一個神經元均可以當作是一個簡單的線性函數,下面咱們構造一個簡單的三層的神經網絡來看看。git

image

如上圖所示,n1 能夠表示爲:github

$$ n_1 = w_{1,1}x_1 + w_{2,1}x_2 + w_{3,1}x_3 + b $$算法

其中 w_{1,1} 表示神經元之間的權重,b 爲一個常量,做爲函數的偏移量。較小的權重能夠弱化某個神經元對下一個神經元形成的影響,而較大的權重將放大信號。假設 w_{1,1} 爲 0.1,w_{3,1} 爲 0.7,那麼 x3 對 n1 的影響要大於 x1。你可能會問,爲何每一個神經元要與其餘全部層的神經元相互鏈接?npm

這裏主要由兩個緣由:編程

  1. 徹底鏈接的形式相對容易的編寫成計算機指令。
  2. 在神經網絡訓練的過程當中會弱化實際上不須要的鏈接(也就是某些鏈接權重會慢慢趨近於 0)。

實際上經過計算獲得 n1 後,其實不能立馬用於後面的計算,還須要通過一個激活函數(通常爲 sigmod 函數)。 json

image

sigmod 函數

其做用主要是引入非線性因素。若是神級網絡中只有上面那種線性函數,不管有多少層,結果始終是線性的。

實際案例

爲了方便計算,咱們構造一個只有兩層的神經網絡,演示一下具體的計算過程。

image

先經過線性函數求得一個 x 值,再把 x 值帶入激活函數,獲得 y1 的值。

$$ x = w_{1,1}x_1 + w_{2,1}x_2 = (1.0 * 0.9) + (0.5 * 0.3) = 1.05 $$

$$ y_1 = 1 / (1 + e ^{-x}) = 1 / (1 + 0.3499) = 0.7408 $$

矩陣乘法

其實上面的計算過程,很容易經過矩陣乘法的方式表示。矩陣這個東西,說簡單點就是一個表格,或者一個二維數組。以下圖所示,就是一個典型的矩陣。

image

那麼矩陣的乘法能夠表示爲:

image

矩陣的乘法一般被成爲點乘或者內積。若是咱們將矩陣內的數字換成咱們神經網絡的輸入和權重,你會發現原來前面的計算如此簡單。

image

得到點積後,只須要代入到激活函數,就能得到輸出了。

image

經過矩陣計算過程能夠表示爲:

$$ X_{hidden} = W_{input\_hidden} · I_{input} O_{hidden} = sigmoid(X_{hidden}) $$

實際案例

下面經過矩陣來表示一個三層神經網絡的計算過程。

image

上圖只給出了輸入層到隱層的計算過程,感興趣能夠本身手動計算下,隱層到輸出層的計算過程。隱層到輸出層的權重矩陣以下:

image

反向傳播

進過一輪神經網絡計算獲得輸出值,一般與咱們實際想要的值是不一致的,這個時候咱們會獲得一個偏差值(偏差值就是訓練數據給出的正確答案與實際輸出值之間的差值)。可是這個偏差是多個節點共同做用的結果,咱們到底該用何種方式來更新各個鏈接的權重呢?這個時候咱們就須要經過反向傳播的方式,求出各個節點的偏差值。

image

下面咱們代入具體值,進行一次計算。

image

上圖中能夠看到 e_1 的偏差值主要由 w_{1,1}w_{2,1} 形成,那麼其偏差應當分散到兩個鏈接上,能夠按照兩個鏈接的權重對偏差 e_1 進行分割。

$$ e_1 * \frac{w_{1,1}}{w_{1,1} + w_{2,1}} = 0.8 * \frac{2}{2 + 3} = 0.32 e_1 * \frac{w_{2,1}}{w_{1,1} + w_{2,1}} = 0.8 * \frac{3}{2 + 3} = 0.48 $$

同理對偏差 e_2 進行分割,而後把兩個鏈接處的偏差值相加,就能獲得輸出點的前饋節點的偏差值。

image

而後在按照以前的方法將這個偏差傳播到前面的層,直到全部節點都能獲得本身的偏差值,這種方式被成爲反向傳播。

使用矩陣乘法進行反向傳播偏差

上面如此繁瑣的操做,咱們也能夠經過矩陣的方式進行簡化。

image

這個矩陣中仍是有麻煩的分數須要處理,那麼咱們能不能大膽一點,將分母直接作歸一化的處理。這麼作咱們僅僅只是改變了反饋偏差的大小,其偏差依舊是按照比例來計算的。

image

image

仔細觀察會發現,與咱們以前計算每層的輸出值的矩陣點擊很像,只是權重矩陣進行翻轉,右上方的元素變成了左下方的元素,咱們能夠稱其爲轉置矩陣,記爲 w^T

反向傳播偏差的矩陣能夠簡單表示爲:

$$ error_{hidden} = W^{T}_{hidden\_output} · error_{output} $$

梯度降低

在每一個點都獲得偏差後,咱們該按照何種方式來更新權重呢?

這個時候就要使用到機器學習中經常使用的方式:梯度下級。

image

更多細節能夠參考我以前寫的博客:梯度降低與線性迴歸

經過不停的訓練,咱們就能改進神經網絡,其本質就是不斷地改變權重的大小,減少神經網絡輸出的偏差值。
最後就可以獲得一個多層神經網絡的模型,經過輸入進行有效的預測。

實戰

環境準備

首先須要安裝 python3 ,直接去 python 官網安裝,儘可能安裝最新版,不推薦安裝 python2 。安裝好 python 環境以後,而後安裝 virtualenv 以及相關依賴。

# 升級 pip 到最新版本
pip3 install --upgrade pip

# 安裝 virtualenv ,用於配置虛擬環境
pip3 install --user --upgrade virtualenv

正常狀況下,當咱們在使用 pip 進行包安裝的時候,都是安裝的全局包,至關於npm install -g。假如如今有兩個項目,項目 A 依賴 simplejson@2 ,項目 B 依賴 simplejson@3,這樣咱們在一臺機器上開發顯得有些手足無措。這個時候 virtualenv 就能大展身手了,virtualenv 能夠建立一個獨立的 python 運行環境,也就是一個沙箱,你甚至能夠在 virtualenv 建立的虛擬環境中使用與當前系統不一樣的 python 版本。

# 配置虛擬環境
cd ~/ml
virtualenv env

# 啓動虛擬環境
# linux
source env/bin/activate
# windows
./env/Scripts/activate

啓動後,以下

(env) λ

image

在虛擬環境下安裝全部模塊依賴。

# 安裝模塊和依賴
(env) λ pip3 install --upgrade jupyter matplotlib numpy scipy
  • jupyter:基於網頁的用於交互計算的應用程序。其可被應用於全過程計算:開發、文檔編寫、運行代碼和展現結果。
  • numpy:數組計算擴展的包,支持高維度數組與矩陣運算,此外也針對數組運算提供大量的數學函數庫。
  • scipy:基於numpy的擴展包,它增長的功能包括數值積分、最優化、統計和一些專用函數。
  • matplotlib:基於numpy的擴展包,提供了豐富的數據繪圖工具,主要用於繪製一些統計圖形。
  • scikit-learn:開源的Python機器學習庫,它基於Numpy和Scipy,提供了大量用於數據挖掘和分析的工具,包括數據預處理、交叉驗證、算法與可視化算法等一系列接口。

啓動 jupyter

jupyter notebook

jupyter 會在8888端口起一個服務,並自動打開瀏覽器。

image

經過右上角的new,你就能建立一個項目了。建立項目後,咱們很方便的在該頁面上進行 python 代碼的運行與輸出。

image

準備數據

MNIST 是由美國的高中生和美國人口調查局的職員手寫數字(0 ~ 9)圖片。接下來要作的事情就是讓咱們的程序學習這些圖片的信息,可以識別出輸入的圖片所表明的數字含義,這聽上去好像有點難度,不着急,咱們一步步來。

這裏準備了 MNIST 的訓練數據,其中 train_100 爲訓練數據集,test_10 爲測試數據集。在機器學習的過程當中,咱們通常會將數據集切分紅兩個,分別爲訓練集合測試集,通常 80% 的數據進行訓練,保留 20% 用於測試。這裏由於是 hello world 操做,咱們只用 100 個數據進行訓練,真實狀況下,這種數據量是遠遠不夠的。

若是想用完整的數據進行訓練,能夠下載這個 csv 文件。

https://pjreddie.com/media/files/mnist_train.csv

觀察數據

下載數據後,將 csv (逗號分隔值文件格式)文件放入到 datasets 文件夾,而後使用 python 進行文件的讀取。

data_file = open("datasets/mnist_train_100.csv", 'r')
data_list = data_file.readlines() # readlines方法用於讀取文件的全部行,並返回一個數組
data_file.close()

len(data_list) # 數組長度爲100

打印第一行文本,看看數據的格式是怎麼樣的

print(data_list[0])
len(data_list[0].split(',')) # 使用 , 進行分割,將字符串轉換爲數組

image

能夠看到一行數據一共有 785 個數據,第一列表示這個手寫數的真實值(這個值在機器學習中稱爲標籤),後面的 784 個數據表示一個 28 * 28 的尺寸的像素值,流行的圖像處理軟件一般用8位表示一個像素,這樣總共有256個灰度等級(像素值在0~255 間),每一個等級表明不一樣的亮度。

下面咱們導入 numpy 庫,對數據進行處理,values[1:] 取出數組的第一位到最後並生成一個新的數組,使用 numpy.asfarray 將數組轉爲一個浮點類型的 ndarray,而後每一項除以 255 在乘以 9,將每一個數字轉爲 0 ~ 9 的個位數,使用 astype(int) 把每一個數再轉爲 int 類型,最後 reshape((28,28) 能夠把數組轉爲 28 * 28 的二維數組。

若是想了解更多 numpy 的資料,能夠查看它的文檔

import numpy as np

values = data_list[3].split(',')
image_array = (np.asfarray(values[1:]) / 255 * 9).astype(int).reshape(28,28)

image

這樣看不夠直觀,接下來使用 matplotlib ,將像素點一個個畫出來。

import matplotlib.pyplot
%matplotlib inline

matplotlib.pyplot.imshow(
    np.asfarray(values[1:]).reshape(28,28), 
    cmap='Greys', 
    interpolation='None'
)

image

搭建神經網絡

咱們簡單勾勒出神經網絡的大概樣子,至少須要三個函數:

  1. 初始化函數——設定輸入層、隱藏層、輸出層節點的數量,隨機生成的權重。
  2. 訓練——學習給定的訓練樣本,調整權重。
  3. 查詢——給定輸入,獲取預測結果。

框架代碼以下:

# 引入依賴庫
import numpy as np
import scipy.special
import matplotlib.pyplot

# 神經網絡類定義
class neuralNetwork:
    # 初始化神經網絡
    def __init__():
        pass

    # 訓練神經網絡
    def train():
        pass
   
    # 查詢神經網絡
    def query():
        pass

初始化神經網絡

接下來讓咱們進行第一步操做,初始化一個神經網絡。

# 初始化神經網絡
    def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
        # 設置輸入層、隱藏層、輸出層節點的數量
        self.inodes = inputnodes
        self.hnodes = hiddennodes
        self.onodes = outputnodes
        
        # 鏈接權重,隨機生成輸入層到隱藏層和隱藏層到輸出層的權重
        self.wih = np.random.rand(self.hnodes, self.inodes) - 0.5
        self.who = np.random.rand(self.onodes, self.hnodes) - 0.5

        # 學習率
        self.lr = learningrate
        
        # 將激活函數設置爲 sigmoid 函數
        self.activation_function = lambda x: scipy.special.expit(x)
        
        pass

生成權重

生成鏈接權重使用 numpy 函數庫,該庫支持大維度數組以及矩陣的運算,經過numpy.random.rand(x, y)能夠快速生成一個 x * y 的矩陣,每一個數字都是一個 0 ~ 1 的隨機數。由於導入庫的時候使用了 import numpy as np 命令,全部代碼中能夠用 np 來代替 numpy

image

上面就是經過 numpy.random.rand 方法生成一個 3 * 3 矩陣的案例。減去0.5是爲了保證生成的權重全部權重都能維持在 -0.5 ~ 0.5 之間的一個隨機值。

image

激活函數

scipy.special 模塊中包含了大量的函數庫,利用 scipy.special 庫能夠很方便快捷的構造出一個激活函數:

activation_function = lambda x: scipy.special.expit(x)

查詢神經網絡

# 查詢神經網絡    
    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

查詢神經網絡的操做很簡單,只須要使用 numpydot 方法對兩個矩陣求點積便可。

這裏有一個知識點,就是關於 numpy 的數據類型,經過 numpy.array 方法可以將 python 中的數組轉爲一個 N 維數組對象 Ndarray,該方法第二個參數就是表示轉化後的維度。

image

上圖是一個普通數組 [1, 2, 3] 使用該方法轉變成二維數組,返回 [[1, 2, 3]]。該方法還有個屬性 T,本質是調用 numpytranspose 方法,對數組進行軸對換,以下圖所示。

image

經過轉置咱們就能獲得一個合適的輸入矩陣了。

image

image

訓練神經網絡

# 訓練神經網絡
    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

訓練神經網絡前半部分與查詢相似,中間會將獲得的差值經過求矩陣點積的方式進行反向傳播,最後就是使用梯度下級的方法修正權重。其中 self.lr 爲梯度降低的學習率,這個值是限制梯度方向的速率,咱們須要常常調整這個值來達到模型的最優解。

進行訓練

# 設置每一層的節點數量
input_nodes = 784
hidden_nodes = 100
output_nodes = 10

# 學習率
learning_rate = 0.1

# 建立神經網絡模型
n = neuralNetwork(input_nodes,hidden_nodes,output_nodes, learning_rate)

# 加載訓練數據
training_data_file = open("datasets/mnist_train_100.csv", 'r')
training_data_list = training_data_file.readlines()
training_data_file.close()

# 訓練神經網絡
# epochs 表示訓練次數
epochs = 10
for e in range(epochs):
    # 遍歷全部數據進行訓練
    for record in training_data_list:
        # 數據經過 ',' 分割,變成一個數組
        all_values = record.split(',')
        # 分離出圖片的像素點到一個單獨數組
        inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
        # 建立目標輸出值(數字 0~9 出現的機率,默認所有爲 0.01)
        targets = np.zeros(output_nodes) + 0.01
        # all_values[0] 表示手寫數字的真實值,將該數字的機率設爲 0.99
        targets[int(all_values[0])] = 0.99
        n.train(inputs, targets)
        pass
    pass

# 訓練完畢
print('done')

驗證訓練結果

# 加載測試數據
test_data_file = open("datasets/mnist_test_10.csv", 'r')
test_data_list = test_data_file.readlines()
test_data_file.close()

# 測試神經網絡
# 記錄全部的訓練值,正確存 1 ,錯誤存 0 。
scorecard = []

# 遍歷全部數據進行測試
for record in test_data_list:
    # 數據經過 ',' 分割,變成一個數組
    all_values = record.split(',')
    # 第一個數字爲正確答案
    correct_label = int(all_values[0])
    # 取出測試的輸入數據
    inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
    # 查詢神經網絡
    outputs = n.query(inputs)
    # 取出機率最大的數字,表示輸出
    label = np.argmax(outputs)
    # 打印出真實值與查詢值
    print('act: ', label, ' pre: ', correct_label)
    if (label == correct_label):
        # 神經網絡查詢結果與真實值匹配,記錄數組存入 1
        scorecard.append(1)
    else:
        # 神經網絡查詢結果與真實值不匹配,記錄數組存入 0
        scorecard.append(0)
        pass
    
    pass
    
# 計算訓練的成功率
scorecard_array = np.asarray(scorecard)
print("performance = ", scorecard_array.sum() / scorecard_array.size)

完整代碼

要查看完整代碼能夠訪問個人 github: deep_neural_network

總結

到這裏整個深度神級網絡的模型原理與實踐已經所有進行完畢了,雖然有些部分概念講解並非那麼仔細,可是你還能夠經過搜索其餘資料瞭解更多。感謝《Python神經網絡編程》這本書,由於它纔有了這個博客,若是感興趣你也能夠買來看看,這本書真的用很簡單的語言描述了複雜的數學計算。

人工智能如今確實是一個很是火熱的階段,但願感興趣的同窗們多多嘗試,可是也不要一昧的追新,忘記了本身原本的優點。

最後附上原文連接:深度神經網絡原理與實踐

相關文章
相關標籤/搜索