摘要:理論基礎(chǔ)什么是神經(jīng)網(wǎng)絡(luò)我們知道深度學習是機器學習的一個分支,是一種以人工神經(jīng)網(wǎng)絡(luò)為架構(gòu),對數(shù)據(jù)進行表征學習的算法。深度神經(jīng)網(wǎng)絡(luò)中的深度指的是一系列連續(xù)的表示層,數(shù)據(jù)模型中包含了多少層,這就被稱為模型的深度。
理論基礎(chǔ) 什么是神經(jīng)網(wǎng)絡(luò)
我們知道深度學習是機器學習的一個分支,是一種以人工神經(jīng)網(wǎng)絡(luò)為架構(gòu),對數(shù)據(jù)進行表征學習的算法。而深度神經(jīng)網(wǎng)絡(luò)又是深度學習的一個分支,它在 wikipedia 上的解釋如下:
深度神經(jīng)網(wǎng)絡(luò)(Deep Neural Networks, DNN)是一種判別模型,具備至少一個隱層的神經(jīng)網(wǎng)絡(luò),可以使用反向傳播算法進行訓練。權(quán)重更新可以使用下式進行隨機梯度下降法求解。
首先我們可以知道,深度神經(jīng)網(wǎng)絡(luò)是一種判別模型。意思就是已知變量 x ,通過判別模型可以推算出 y。比如機器學習中常用到的案例,通過手寫數(shù)字,模型推斷出手寫的是數(shù)字幾。
深度神經(jīng)網(wǎng)絡(luò)中的“深度”指的是一系列連續(xù)的表示層,數(shù)據(jù)模型中包含了多少層,這就被稱為模型的“深度”。通過這些層我們可以對數(shù)據(jù)進行高層的抽象。如下圖所示,深度神級網(wǎng)絡(luò)由一個輸入層,多個(至少一個)隱層,以及一個輸出層構(gòu)成,而且輸入層與輸出層的數(shù)量不一定是對等的。每一層都有若干個神經(jīng)元,神經(jīng)元之間有連接權(quán)重。
還是上面的案例,識別手寫數(shù)字,手寫的數(shù)字要怎么轉(zhuǎn)成輸入呢?既然是手寫,那么肯定是一張圖片,圖片由多個像素點組成,這些像素點可以構(gòu)成一個輸入,經(jīng)過多層神經(jīng)網(wǎng)絡(luò),輸出10個數(shù)字,這個10個數(shù)字就代表了數(shù)字 0 ~ 9 的概率。
神經(jīng)元如何輸入輸出神經(jīng)網(wǎng)絡(luò)中的每個神經(jīng)元都可以看成是一個簡單的線性函數(shù),下面我們構(gòu)造一個簡單的三層的神經(jīng)網(wǎng)絡(luò)來看看。
如上圖所示,n1 可以表示為:
$$ n_1 = w_{1,1}x_1 + w_{2,1}x_2 + w_{3,1}x_3 + b $$
其中 w_{1,1} 表示神經(jīng)元之間的權(quán)重,b 為一個常量,作為函數(shù)的偏移量。較小的權(quán)重可以弱化某個神經(jīng)元對下一個神經(jīng)元造成的影響,而較大的權(quán)重將放大信號。假設(shè) w_{1,1} 為 0.1,w_{3,1} 為 0.7,那么 x3 對 n1 的影響要大于 x1。你可能會問,為什么每個神經(jīng)元要與其他所有層的神經(jīng)元相互連接?
這里主要由兩個原因:
完全連接的形式相對容易的編寫成計算機指令。
在神經(jīng)網(wǎng)絡(luò)訓練的過程中會弱化實際上不需要的連接(也就是某些連接權(quán)重會慢慢趨近于 0)。
實際上通過計算得到 n1 后,其實不能立馬用于后面的計算,還需要經(jīng)過一個激活函數(shù)(一般為 sigmod 函數(shù))。
其作用主要是引入非線性因素。如果神級網(wǎng)絡(luò)中只有上面那種線性函數(shù),無論有多少層,結(jié)果始終是線性的。
實際案例為了方便計算,我們構(gòu)造一個只有兩層的神經(jīng)網(wǎng)絡(luò),演示一下具體的計算過程。
先通過線性函數(shù)求得一個 x 值,再把 x 值帶入激活函數(shù),得到 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 $$
矩陣乘法其實上面的計算過程,很容易通過矩陣乘法的方式表示。矩陣這個東西,說簡單點就是一個表格,或者一個二維數(shù)組。如下圖所示,就是一個典型的矩陣。
那么矩陣的乘法可以表示為:
矩陣的乘法通常被成為點乘或者內(nèi)積。如果我們將矩陣內(nèi)的數(shù)字換成我們神經(jīng)網(wǎng)絡(luò)的輸入和權(quán)重,你會發(fā)現(xiàn)原來前面的計算如此簡單。
獲得點積后,只需要代入到激活函數(shù),就能獲得輸出了。
通過矩陣計算過程可以表示為:
$$ X_{hidden} = W_{input\_hidden} · I_{input} O_{hidden} = sigmoid(X_{hidden}) $$
實際案例下面通過矩陣來表示一個三層神經(jīng)網(wǎng)絡(luò)的計算過程。
上圖只給出了輸入層到隱層的計算過程,感興趣可以自己手動計算下,隱層到輸出層的計算過程。隱層到輸出層的權(quán)重矩陣如下:
反向傳播進過一輪神經(jīng)網(wǎng)絡(luò)計算得到輸出值,通常與我們實際想要的值是不一致的,這個時候我們會得到一個誤差值(誤差值就是訓練數(shù)據(jù)給出的正確答案與實際輸出值之間的差值)。但是這個誤差是多個節(jié)點共同作用的結(jié)果,我們到底該用何種方式來更新各個連接的權(quán)重呢?這個時候我們就需要通過反向傳播的方式,求出各個節(jié)點的誤差值。
下面我們代入具體值,進行一次計算。
上圖中可以看到 e_1 的誤差值主要由 w_{1,1} 和 w_{2,1} 造成,那么其誤差應當分散到兩個連接上,可以按照兩個連接的權(quán)重對誤差 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 進行分割,然后把兩個連接處的誤差值相加,就能得到輸出點的前饋節(jié)點的誤差值。
然后在按照之前的方法將這個誤差傳播到前面的層,直到所有節(jié)點都能得到自己的誤差值,這種方式被成為反向傳播。
使用矩陣乘法進行反向傳播誤差上面如此繁瑣的操作,我們也可以通過矩陣的方式進行簡化。
這個矩陣中還是有麻煩的分數(shù)需要處理,那么我們能不能大膽一點,將分母直接做歸一化的處理。這么做我們僅僅只是改變了反饋誤差的大小,其誤差依舊是按照比例來計算的。
仔細觀察會發(fā)現(xiàn),與我們之前計算每層的輸出值的矩陣點擊很像,只是權(quán)重矩陣進行翻轉(zhuǎn),右上方的元素變成了左下方的元素,我們可以稱其為轉(zhuǎn)置矩陣,記為 w^T 。
反向傳播誤差的矩陣可以簡單表示為:
$$ error_{hidden} = W^{T}_{hidden\_output} · error_{output} $$
梯度下降在每個點都得到誤差后,我們該按照何種方式來更新權(quán)重呢?
這個時候就要使用到機器學習中常用的方式:梯度下級。
更多細節(jié)可以參考我之前寫的博客:梯度下降與線性回歸
通過不停的訓練,我們就能改進神經(jīng)網(wǎng)絡(luò),其本質(zhì)就是不斷地改變權(quán)重的大小,減小神經(jīng)網(wǎng)絡(luò)輸出的誤差值。
最后就能夠得到一個多層神經(jīng)網(wǎng)絡(luò)的模型,通過輸入進行有效的預測。
首先需要安裝 python3 ,直接去 python 官網(wǎng)安裝,盡量安裝最新版,不推薦安裝 python2 。安裝好 python 環(huán)境之后,然后安裝 virtualenv 以及相關(guān)依賴。
# 升級 pip 到最新版本 pip3 install --upgrade pip # 安裝 virtualenv ,用于配置虛擬環(huán)境 pip3 install --user --upgrade virtualenv
正常情況下,當我們在使用 pip 進行包安裝的時候,都是安裝的全局包,相當于npm install -g。假如現(xiàn)在有兩個項目,項目 A 依賴 simplejson@2 ,項目 B 依賴 simplejson@3,這樣我們在一臺機器上開發(fā)顯得有些手足無措。這個時候 virtualenv 就能大展身手了,virtualenv 可以創(chuàng)建一個獨立的 python 運行環(huán)境,也就是一個沙箱,你甚至可以在 virtualenv 創(chuàng)建的虛擬環(huán)境中使用與當前系統(tǒng)不同的 python 版本。
# 配置虛擬環(huán)境 cd ~/ml virtualenv env # 啟動虛擬環(huán)境 # linux source env/bin/activate # windows ./env/Scripts/activate
啟動后,如下
(env) λ
在虛擬環(huán)境下安裝所有模塊依賴。
# 安裝模塊和依賴 (env) λ pip3 install --upgrade jupyter matplotlib numpy scipy
jupyter:基于網(wǎng)頁的用于交互計算的應用程序。其可被應用于全過程計算:開發(fā)、文檔編寫、運行代碼和展示結(jié)果。
numpy:數(shù)組計算擴展的包,支持高維度數(shù)組與矩陣運算,此外也針對數(shù)組運算提供大量的數(shù)學函數(shù)庫。
scipy:基于numpy的擴展包,它增加的功能包括數(shù)值積分、最優(yōu)化、統(tǒng)計和一些專用函數(shù)。
matplotlib:基于numpy的擴展包,提供了豐富的數(shù)據(jù)繪圖工具,主要用于繪制一些統(tǒng)計圖形。
scikit-learn:開源的Python機器學習庫,它基于Numpy和Scipy,提供了大量用于數(shù)據(jù)挖掘和分析的工具,包括數(shù)據(jù)預處理、交叉驗證、算法與可視化算法等一系列接口。
啟動 jupyterjupyter notebook
jupyter 會在8888端口起一個服務,并自動打開瀏覽器。
通過右上角的new,你就能創(chuàng)建一個項目了。創(chuàng)建項目后,我們很方便的在該頁面上進行 python 代碼的運行與輸出。
準備數(shù)據(jù)MNIST 是由美國的高中生和美國人口調(diào)查局的職員手寫數(shù)字(0 ~ 9)圖片。接下來要做的事情就是讓我們的程序?qū)W習這些圖片的信息,能夠識別出輸入的圖片所代表的數(shù)字含義,這聽上去好像有點難度,不著急,我們一步步來。
這里準備了 MNIST 的訓練數(shù)據(jù),其中 train_100 為訓練數(shù)據(jù)集,test_10 為測試數(shù)據(jù)集。在機器學習的過程中,我們一般會將數(shù)據(jù)集切分成兩個,分別為訓練集合測試集,一般 80% 的數(shù)據(jù)進行訓練,保留 20% 用于測試。這里因為是 hello world 操作,我們只用 100 個數(shù)據(jù)進行訓練,真實情況下,這種數(shù)據(jù)量是遠遠不夠的。
mnist_train_100.csv
mnist_test_10.csv
如果想用完整的數(shù)據(jù)進行訓練,可以下載這個 csv 文件。
https://pjreddie.com/media/files/mnist_train.csv
觀察數(shù)據(jù)下載數(shù)據(jù)后,將 csv (逗號分隔值文件格式)文件放入到 datasets 文件夾,然后使用 python 進行文件的讀取。
data_file = open("datasets/mnist_train_100.csv", "r") data_list = data_file.readlines() # readlines方法用于讀取文件的所有行,并返回一個數(shù)組 data_file.close() len(data_list) # 數(shù)組長度為100
打印第一行文本,看看數(shù)據(jù)的格式是怎么樣的
print(data_list[0]) len(data_list[0].split(",")) # 使用 , 進行分割,將字符串轉(zhuǎn)換為數(shù)組
可以看到一行數(shù)據(jù)一共有 785 個數(shù)據(jù),第一列表示這個手寫數(shù)的真實值(這個值在機器學習中稱為標簽),后面的 784 個數(shù)據(jù)表示一個 28 * 28 的尺寸的像素值,流行的圖像處理軟件通常用8位表示一個像素,這樣總共有256個灰度等級(像素值在0~255 間),每個等級代表不同的亮度。
下面我們導入 numpy 庫,對數(shù)據(jù)進行處理,values[1:] 取出數(shù)組的第一位到最后并生成一個新的數(shù)組,使用 numpy.asfarray 將數(shù)組轉(zhuǎn)為一個浮點類型的 ndarray,然后每一項除以 255 在乘以 9,將每個數(shù)字轉(zhuǎn)為 0 ~ 9 的個位數(shù),使用 astype(int) 把每個數(shù)再轉(zhuǎn)為 int 類型,最后 reshape((28,28) 可以把數(shù)組轉(zhuǎn)為 28 * 28 的二維數(shù)組。
如果想了解更多 numpy 的資料,可以查看它的文檔。
import numpy as np values = data_list[3].split(",") image_array = (np.asfarray(values[1:]) / 255 * 9).astype(int).reshape(28,28)
這樣看不夠直觀,接下來使用 matplotlib ,將像素點一個個畫出來。
import matplotlib.pyplot %matplotlib inline matplotlib.pyplot.imshow( np.asfarray(values[1:]).reshape(28,28), cmap="Greys", interpolation="None" )搭建神經(jīng)網(wǎng)絡(luò)
我們簡單勾勒出神經(jīng)網(wǎng)絡(luò)的大概樣子,至少需要三個函數(shù):
初始化函數(shù)——設(shè)定輸入層、隱藏層、輸出層節(jié)點的數(shù)量,隨機生成的權(quán)重。
訓練——學習給定的訓練樣本,調(diào)整權(quán)重。
查詢——給定輸入,獲取預測結(jié)果。
框架代碼如下:
# 引入依賴庫 import numpy as np import scipy.special import matplotlib.pyplot # 神經(jīng)網(wǎng)絡(luò)類定義 class neuralNetwork: # 初始化神經(jīng)網(wǎng)絡(luò) def __init__(): pass # 訓練神經(jīng)網(wǎng)絡(luò) def train(): pass # 查詢神經(jīng)網(wǎng)絡(luò) def query(): pass初始化神經(jīng)網(wǎng)絡(luò)
接下來讓我們進行第一步操作,初始化一個神經(jīng)網(wǎng)絡(luò)。
# 初始化神經(jīng)網(wǎng)絡(luò) def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate): # 設(shè)置輸入層、隱藏層、輸出層節(jié)點的數(shù)量 self.inodes = inputnodes self.hnodes = hiddennodes self.onodes = outputnodes # 連接權(quán)重,隨機生成輸入層到隱藏層和隱藏層到輸出層的權(quán)重 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 # 將激活函數(shù)設(shè)置為 sigmoid 函數(shù) self.activation_function = lambda x: scipy.special.expit(x) pass
生成權(quán)重
生成連接權(quán)重使用 numpy 函數(shù)庫,該庫支持大維度數(shù)組以及矩陣的運算,通過numpy.random.rand(x, y)可以快速生成一個 x * y 的矩陣,每個數(shù)字都是一個 0 ~ 1 的隨機數(shù)。因為導入庫的時候使用了 import numpy as np 命令,所有代碼中可以用 np 來代替 numpy。
上面就是通過 numpy.random.rand 方法生成一個 3 * 3 矩陣的案例。減去0.5是為了保證生成的權(quán)重所有權(quán)重都能維持在 -0.5 ~ 0.5 之間的一個隨機值。
激活函數(shù)
scipy.special 模塊中包含了大量的函數(shù)庫,利用 scipy.special 庫可以很方便快捷的構(gòu)造出一個激活函數(shù):
activation_function = lambda x: scipy.special.expit(x)查詢神經(jīng)網(wǎng)絡(luò)
# 查詢神經(jīng)網(wǎng)絡(luò) def query(self, inputs_list): # 將輸入的數(shù)組轉(zhuǎn)化為一個二維數(shù)組 inputs = np.array(inputs_list, ndmin=2).T # 計算輸入數(shù)據(jù)與權(quán)重的點積 hidden_inputs = np.dot(self.wih, inputs) # 經(jīng)過激活函數(shù)的到隱藏層數(shù)據(jù) hidden_outputs = self.activation_function(hidden_inputs) # 計算隱藏層數(shù)據(jù)與權(quán)重的點積 final_inputs = np.dot(self.who, hidden_outputs) # 最終到達輸出層的數(shù)據(jù) final_outputs = self.activation_function(final_inputs) return final_outputs
查詢神經(jīng)網(wǎng)絡(luò)的操作很簡單,只需要使用 numpy 的 dot 方法對兩個矩陣求點積即可。
這里有一個知識點,就是關(guān)于 numpy 的數(shù)據(jù)類型,通過 numpy.array 方法能夠?qū)?python 中的數(shù)組轉(zhuǎn)為一個 N 維數(shù)組對象 Ndarray,該方法第二個參數(shù)就是表示轉(zhuǎn)化后的維度。
上圖是一個普通數(shù)組 [1, 2, 3] 使用該方法轉(zhuǎn)變成二維數(shù)組,返回 [[1, 2, 3]]。該方法還有個屬性 T,本質(zhì)是調(diào)用 numpy 的 transpose 方法,對數(shù)組進行軸對換,如下圖所示。
通過轉(zhuǎn)置我們就能得到一個合適的輸入矩陣了。
訓練神經(jīng)網(wǎng)絡(luò)# 訓練神經(jīng)網(wǎng)絡(luò) def train(self, inputs_list, targets_list): # 將輸入數(shù)據(jù)與目標數(shù)據(jù)轉(zhuǎn)為二維數(shù)組 inputs = np.array(inputs_list, ndmin=2).T targets = np.array(targets_list, ndmin=2).T # 通過矩陣點積和激活函數(shù)得到隱藏層的輸出 hidden_inputs = np.dot(self.wih, inputs) hidden_outputs = self.activation_function(hidden_inputs) # 通過矩陣點積和激活函數(shù)得到最終輸出 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) # 通過梯度下降法更新隱藏層到輸出層的權(quán)重 self.who += self.lr * np.dot( (output_errors * final_outputs * (1.0 - final_outputs)), np.transpose(hidden_outputs) ) # 通過梯度下降法更新輸入層到隱藏層的權(quán)重 self.wih += self.lr * np.dot( (hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), np.transpose(inputs) ) pass
訓練神經(jīng)網(wǎng)絡(luò)前半部分與查詢類似,中間會將得到的差值通過求矩陣點積的方式進行反向傳播,最后就是使用梯度下級的方法修正權(quán)重。其中 self.lr 為梯度下降的學習率,這個值是限制梯度方向的速率,我們需要經(jīng)常調(diào)整這個值來達到模型的最優(yōu)解。
進行訓練# 設(shè)置每一層的節(jié)點數(shù)量 input_nodes = 784 hidden_nodes = 100 output_nodes = 10 # 學習率 learning_rate = 0.1 # 創(chuàng)建神經(jīng)網(wǎng)絡(luò)模型 n = neuralNetwork(input_nodes,hidden_nodes,output_nodes, learning_rate) # 加載訓練數(shù)據(jù) training_data_file = open("datasets/mnist_train_100.csv", "r") training_data_list = training_data_file.readlines() training_data_file.close() # 訓練神經(jīng)網(wǎng)絡(luò) # epochs 表示訓練次數(shù) epochs = 10 for e in range(epochs): # 遍歷所有數(shù)據(jù)進行訓練 for record in training_data_list: # 數(shù)據(jù)通過 "," 分割,變成一個數(shù)組 all_values = record.split(",") # 分離出圖片的像素點到一個多帶帶數(shù)組 inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01 # 創(chuàng)建目標輸出值(數(shù)字 0~9 出現(xiàn)的概率,默認全部為 0.01) targets = np.zeros(output_nodes) + 0.01 # all_values[0] 表示手寫數(shù)字的真實值,將該數(shù)字的概率設(shè)為 0.99 targets[int(all_values[0])] = 0.99 n.train(inputs, targets) pass pass # 訓練完畢 print("done")驗證訓練結(jié)果
# 加載測試數(shù)據(jù) test_data_file = open("datasets/mnist_test_10.csv", "r") test_data_list = test_data_file.readlines() test_data_file.close() # 測試神經(jīng)網(wǎng)絡(luò) # 記錄所有的訓練值,正確存 1 ,錯誤存 0 。 scorecard = [] # 遍歷所有數(shù)據(jù)進行測試 for record in test_data_list: # 數(shù)據(jù)通過 "," 分割,變成一個數(shù)組 all_values = record.split(",") # 第一個數(shù)字為正確答案 correct_label = int(all_values[0]) # 取出測試的輸入數(shù)據(jù) inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01 # 查詢神經(jīng)網(wǎng)絡(luò) outputs = n.query(inputs) # 取出概率最大的數(shù)字,表示輸出 label = np.argmax(outputs) # 打印出真實值與查詢值 print("act: ", label, " pre: ", correct_label) if (label == correct_label): # 神經(jīng)網(wǎng)絡(luò)查詢結(jié)果與真實值匹配,記錄數(shù)組存入 1 scorecard.append(1) else: # 神經(jīng)網(wǎng)絡(luò)查詢結(jié)果與真實值不匹配,記錄數(shù)組存入 0 scorecard.append(0) pass pass # 計算訓練的成功率 scorecard_array = np.asarray(scorecard) print("performance = ", scorecard_array.sum() / scorecard_array.size)完整代碼
要查看完整代碼可以訪問我的 github: deep_neural_network
總結(jié)到這里整個深度神級網(wǎng)絡(luò)的模型原理與實踐已經(jīng)全部進行完畢了,雖然有些部分概念講解并不是那么仔細,但是你還可以通過搜索其他資料了解更多。感謝《Python神經(jīng)網(wǎng)絡(luò)編程》這本書,因為它才有了這個博客,如果感興趣你也可以買來看看,這本書真的用很簡單的語言描述了復雜的數(shù)學計算。
人工智能現(xiàn)在確實是一個非?;馃岬碾A段,希望感興趣的同學們多多嘗試,但是也不要一昧的追新,忘記了自己本來的優(yōu)勢。
最后附上原文鏈接:深度神經(jīng)網(wǎng)絡(luò)原理與實踐
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/43398.html
閱讀 2349·2021-11-24 09:39
閱讀 3794·2021-11-19 09:40
閱讀 2166·2021-09-27 13:36
閱讀 1907·2019-08-30 15:44
閱讀 404·2019-08-30 13:52
閱讀 2720·2019-08-30 11:13
閱讀 2202·2019-08-29 16:18
閱讀 1768·2019-08-29 15:43