Q:什么是人工神經(jīng)網(wǎng)絡(luò)?
人工神經(jīng)網(wǎng)絡(luò)是科學(xué)家模擬人類大腦的神經(jīng)網(wǎng)絡(luò)建立的數(shù)學(xué)模型。人工神經(jīng)網(wǎng)絡(luò)由一個(gè)個(gè)“人工神經(jīng)元”組合而成。“人工神經(jīng)元”也是一個(gè)數(shù)學(xué)模型,其本質(zhì)是一個(gè)函數(shù)。所以人工神經(jīng)網(wǎng)絡(luò)的本質(zhì)也是一個(gè)函數(shù),而且是一個(gè)復(fù)雜的,包含很多變量和參數(shù)的函數(shù)。

Q:什么是人工神經(jīng)元?
人工神經(jīng)元是人工神經(jīng)網(wǎng)絡(luò)的基本單元,其本質(zhì)也是一個(gè)函數(shù)。類似人類大腦的神經(jīng)元,人工神經(jīng)元模型也有樹突、軸突、神經(jīng)元中心等結(jié)構(gòu)。最經(jīng)典的神經(jīng)元模型是“M-P神經(jīng)元模型”。

一個(gè)神經(jīng)元接收來自其他神經(jīng)元傳來的信號(hào)(變量),通過樹突傳輸(參數(shù))到神經(jīng)元中心(參數(shù)),經(jīng)過轉(zhuǎn)換后(映射f),通過突觸傳遞出去(函數(shù)值)。上圖中紅色的函數(shù)表達(dá)式就是一個(gè)人工神經(jīng)元的數(shù)學(xué)表達(dá)。如果把上圖的激活函數(shù)f設(shè)置成sigmoid函數(shù),那么一個(gè)神經(jīng)元本質(zhì)上就是一個(gè)logistic 回歸模型。
由上述神經(jīng)元工作過程可知,一個(gè)神經(jīng)元可以接受多個(gè)變量,輸出一個(gè)結(jié)果。一個(gè)神經(jīng)元的輸出也可能是另一個(gè)神經(jīng)元的輸入。所以一個(gè)由多個(gè)神經(jīng)元組成的神經(jīng)網(wǎng)絡(luò),本質(zhì)上就是一個(gè)復(fù)雜的、多層嵌套的復(fù)合函數(shù)。
Q:為什么神經(jīng)網(wǎng)絡(luò)要加入激活函數(shù)?
或問:神經(jīng)網(wǎng)絡(luò)引入非線性函數(shù)的用意是什么?
引入激活函數(shù)的根本目的是提高學(xué)習(xí)模型的學(xué)習(xí)(擬合數(shù)據(jù))能力,其最初的動(dòng)機(jī)是彌補(bǔ)線性模型學(xué)習(xí)能力不足的問題。
我們學(xué)習(xí)機(jī)器學(xué)習(xí),都是從最簡(jiǎn)單的線性模型開始的(也有人從K臨近算法開始)。我們知道線性模型雖然簡(jiǎn)單,但是功能卻不差,起碼預(yù)測(cè)一下波士頓的房?jī)r(jià)或者判斷一下是否糖尿病是沒問題的。但是線性模型說到底也只是一條直線或者一個(gè)直的平面,但是現(xiàn)實(shí)中的數(shù)據(jù)卻不都是能用直線擬合或者能用平面分隔開的。比如依據(jù)培養(yǎng)時(shí)間預(yù)測(cè)細(xì)胞數(shù)目(函數(shù)圖像為S型曲線,類似tanh)就不能用線性模型了,或者說線性模型的效果就很差了。
一句話,線性組合能力有限,不能表示復(fù)雜的規(guī)律;線性模型先天不足,不能學(xué)習(xí)復(fù)雜的情況。就像我們不能指望一個(gè)普通的6個(gè)月嬰兒能掌握勾股定理,也不能指望一個(gè)普通的兩歲小孩能理解量子力學(xué)。
所以我們?cè)诰€性模型外面包上一層非線性函數(shù),其實(shí)就是使其結(jié)構(gòu)更加復(fù)雜,使得改造后的(廣義)線性模型有能力表示復(fù)雜的映射,有能力學(xué)習(xí)到更復(fù)雜的情況,有能力擬合呈線性關(guān)系數(shù)據(jù),也能擬合不呈線性關(guān)系的數(shù)據(jù)。
Q:神經(jīng)網(wǎng)絡(luò)有哪些分類?
按照神經(jīng)元的層數(shù)來分,可以分為只有輸入、輸出層的單層網(wǎng)絡(luò)(不計(jì)算輸入層),代表是感知機(jī);以及包含隱含層的多層網(wǎng)絡(luò)。
按照網(wǎng)絡(luò)結(jié)構(gòu)來分,可以分為徑向基函數(shù)網(wǎng)絡(luò)(RBF)、競(jìng)爭(zhēng)學(xué)習(xí)網(wǎng)絡(luò)(ART)、自組織映射網(wǎng)絡(luò)(SOM)、級(jí)聯(lián)相關(guān)網(wǎng)絡(luò)、遞歸神經(jīng)網(wǎng)絡(luò)(RNN)、Boltzman機(jī)等各種各樣紛繁復(fù)雜的種類。
Q:感知機(jī)有什么作用?怎么訓(xùn)練一個(gè)感知機(jī)?
感知機(jī)是最簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò),只有輸入層和輸出層。從數(shù)學(xué)形式上看,感知機(jī)就是只有一個(gè)神經(jīng)元的神經(jīng)網(wǎng)絡(luò)。一般來說,感知機(jī)用來進(jìn)行二分類任務(wù),或者實(shí)現(xiàn)邏輯“與”、“或”、“非”的操作。

感知機(jī)模型 的輸出一般是二值的,即1或-1。
感知機(jī)的學(xué)習(xí)的學(xué)習(xí)規(guī)則很簡(jiǎn)單,也是類似于線性回歸一樣,使用梯度下降的思想,每讀取一個(gè)樣本,計(jì)算一次預(yù)測(cè)值,就調(diào)整一次參數(shù)(權(quán)值)。

表示第i個(gè)輸入,
表示第i個(gè)輸入對(duì)應(yīng)的參數(shù)(權(quán)值)。
Q:神經(jīng)元如何組成神經(jīng)網(wǎng)絡(luò)?
通常來說,神經(jīng)元會(huì)按層次結(jié)構(gòu)組成一個(gè)網(wǎng)絡(luò),也就是一個(gè)多部圖結(jié)構(gòu):每一層有任意數(shù)量個(gè)節(jié)點(diǎn),但是同一層節(jié)點(diǎn)之間沒有直接相連,只會(huì)和相鄰層的結(jié)點(diǎn)直接相連,這也是經(jīng)典的前饋神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)。

如此一來,本層的節(jié)點(diǎn)接收前一層節(jié)點(diǎn)的輸出作為輸入,經(jīng)過計(jì)算后輸出給后一層節(jié)點(diǎn),每一層節(jié)點(diǎn)的輸出結(jié)構(gòu)都會(huì)是后一層節(jié)點(diǎn)的輸入。
前饋神經(jīng)網(wǎng)絡(luò)只是最基本的網(wǎng)絡(luò)架構(gòu),在此基礎(chǔ)上有多種不同的變體:
競(jìng)爭(zhēng)神經(jīng)網(wǎng)絡(luò)
競(jìng)爭(zhēng)型網(wǎng)絡(luò)的輸出層之間會(huì)有直接聯(lián)系,每個(gè)神經(jīng)元都會(huì)和其他神經(jīng)元競(jìng)爭(zhēng),勝利者才能順利輸出,落敗者沒有輸出。典型的模型有自組織映射網(wǎng)絡(luò)(SOM)。

級(jí)聯(lián)相關(guān)網(wǎng)絡(luò)
前饋網(wǎng)絡(luò)的網(wǎng)絡(luò)結(jié)構(gòu)一般在訓(xùn)練開始前就設(shè)置好,訓(xùn)練過程中不會(huì)改變。級(jí)聯(lián)相關(guān)網(wǎng)絡(luò)則是可以在訓(xùn)練過程中動(dòng)態(tài)改變網(wǎng)絡(luò)結(jié)構(gòu):新增隱藏層和隱藏節(jié)點(diǎn)。

遞歸神經(jīng)網(wǎng)絡(luò)
前饋網(wǎng)絡(luò)每一層的輸入都是來自前一層的輸出,而遞歸神經(jīng)網(wǎng)絡(luò)每一層的輸入除了前一層的輸出,還有本層在上一輪訓(xùn)練的輸出,因此特別適合對(duì)序列數(shù)據(jù)(如文本數(shù)據(jù))建模。Elman網(wǎng)絡(luò)是常用的遞歸神經(jīng)網(wǎng)絡(luò)模型。

波茲曼機(jī)
波茲曼機(jī)只有兩層,可見層和隱含層。標(biāo)準(zhǔn)的波茲曼機(jī)是一個(gè)全連接網(wǎng)絡(luò),每個(gè)神經(jīng)元都和其他所有神經(jīng)元相連,顯然這種網(wǎng)絡(luò)的復(fù)雜度就很高,難以訓(xùn)練。而受限波茲曼機(jī)則是同一層之間的神經(jīng)元沒有連接,類似一個(gè)二層前饋神經(jīng)網(wǎng)絡(luò)(不包含輸入層)。

Q:神經(jīng)網(wǎng)絡(luò)訓(xùn)練過程是怎樣的?
多層神經(jīng)網(wǎng)絡(luò)由于神經(jīng)元層數(shù)比單層多,所以參數(shù)更多,也意味著學(xué)習(xí)能力更強(qiáng),所以能夠勝任圖像處理、語音識(shí)別等任務(wù)。
最經(jīng)典的多層網(wǎng)絡(luò)訓(xùn)練算法是“誤差逆?zhèn)鞑ニ惴ǎ˙P)”。其基本思想也是通過構(gòu)建關(guān)于網(wǎng)絡(luò)權(quán)重參數(shù)的損失函數(shù)來衡量神經(jīng)網(wǎng)絡(luò)的誤差,然后通過梯度下降令損失函數(shù)取最小值,求得網(wǎng)絡(luò)權(quán)重參數(shù)的最優(yōu)值。
多層神經(jīng)網(wǎng)絡(luò)的參數(shù)也是按層劃分的,所以參數(shù)的求解也要一層一層地進(jìn)行。首先求出輸出層的各個(gè)參數(shù),求出倒數(shù)第二層的參數(shù),再求解出倒數(shù)第三層的參數(shù),以此類推,求解出所有層的參數(shù)。
由于參數(shù)是從輸出層往后求解的,并且是通過損失函數(shù)計(jì)算出來的,因此成為誤差反向傳播算法。

下面是誤差反向傳播算法的數(shù)學(xué)推導(dǎo)
由于反向傳播算法應(yīng)用于復(fù)雜網(wǎng)絡(luò)時(shí)會(huì)產(chǎn)生巨量的計(jì)算,為闡釋清楚算法過程,這里把西瓜書上給出的示例網(wǎng)絡(luò)進(jìn)一步簡(jiǎn)化:整個(gè)網(wǎng)絡(luò)只有輸入層、一個(gè)隱含層,和輸出層,每層只有一個(gè)神經(jīng)元。訓(xùn)練樣本也只有一個(gè):,其中x是一個(gè)實(shí)數(shù)(實(shí)際上應(yīng)該是一維向量)。

在開始理解數(shù)學(xué)推導(dǎo)前,我們要先明確作者使用的數(shù)學(xué)符號(hào)所代表的含義:
-
訓(xùn)練集的樣本數(shù)據(jù)。在這里只是一個(gè)實(shí)數(shù)。
-
訓(xùn)練集的標(biāo)記數(shù)據(jù),也就是實(shí)際結(jié)果。在這里只是一個(gè)實(shí)數(shù)(二分類任務(wù)的話就只有0或1)。
-
輸入層和隱含層之間的連接權(quán)重(weights)
-
輸入層和隱含層之間的連接偏置/閾值(bias)
-
隱含層的線性組合計(jì)算值
-
Sigmoid函數(shù):
-
隱含層的輸出結(jié)果,也是輸出層的輸入值
-
隱含層和輸出層之間的連接權(quán)重(weights)
-
隱含層和輸出層之間的連接偏置/閾值(bias)
-
輸出層的線性組合計(jì)算值
-
輸出層的輸出結(jié)果,也是神經(jīng)網(wǎng)絡(luò)的輸出值,也就是預(yù)測(cè)結(jié)果 。
首先我們要拿到誤差,然后才能反向傳播
把訓(xùn)練樣本輸入神經(jīng)網(wǎng)絡(luò)后,我們得到網(wǎng)絡(luò)的預(yù)測(cè)值
,然后我們可以用均方誤差公式來計(jì)算本輪訓(xùn)練的誤差:
然后我們可以利用這個(gè)誤差值來著手更新隱含層到輸出層的連接權(quán)重。
更新隱含層到輸出層的參數(shù)w和
按照梯度下降算法,和
的更新公式如下。其中
是學(xué)習(xí)率,一個(gè)我們自己定義的超參數(shù)。整個(gè)更新公式的含義就是讓參數(shù)朝著學(xué)習(xí)誤差E最小的方向(正是梯度的幾何意義)移動(dòng)一點(diǎn),至于移動(dòng)的步子多大,由學(xué)習(xí)率
決定。
和
這個(gè)又該怎么算?由于
通過導(dǎo)數(shù)的鏈?zhǔn)椒▌t,有
其中
因此
完成這一層后,我們接著更新后一層的參數(shù),亦即更新輸入層和隱含層之間的權(quán)重參數(shù)。
更新隱含層到輸出層的參數(shù)v和
按照梯度下降算法,和
的更新公式如下。
和
又該怎么算?按照類似的流程,由于
通過導(dǎo)數(shù)的鏈?zhǔn)椒▌t,有
總結(jié)BP算法流程
對(duì)于上面我們給出的簡(jiǎn)化3層感知機(jī),以及只有一個(gè)訓(xùn)練樣本的情況,我們要循環(huán)往復(fù)地進(jìn)行如下計(jì)算
一直計(jì)算到給定的循環(huán)次數(shù),或者這一輪的誤差與前一輪地誤差相差小于一個(gè)閾值為止。
以上便是誤差反向傳播算法在3層感知機(jī),且每層只有一個(gè)神經(jīng)元的情況下的數(shù)學(xué)推導(dǎo)。對(duì)于多層網(wǎng)絡(luò),每層多個(gè)神經(jīng)元的情況,數(shù)學(xué)原理相同,具體可以參考吳恩達(dá)的機(jī)器學(xué)習(xí)課程或深度學(xué)習(xí)課程。
Talk is cheap, show me the code!
下面代碼是按照西瓜書里的案例,實(shí)現(xiàn)的一個(gè)極其簡(jiǎn)陋的3層感知機(jī)分類器。這個(gè)模型沒有momentum,沒有penalty,沒有adaptive,其他優(yōu)化手段一切都沒有,就只是一個(gè)簡(jiǎn)陋的網(wǎng)絡(luò)結(jié)構(gòu)。本來西瓜書里兩層的激活函數(shù)都是sigmoid,后來測(cè)試的時(shí)候怎么測(cè)準(zhǔn)確率都在0.56左右浮動(dòng),實(shí)在忍不了,把第一層的激活函數(shù)換了ReLU,這才好看一點(diǎn),但仍然被其他模型吊打??赡苁沁@份代碼里面的bug詞太多了吧?
"""
Naive 3 layer perceptron, uneffective, full of flaws!
:file: supervised.py
:author: Richy Zhu
:email: rickyzhu@foxmail.com
"""
import numpy as np
def binarize(y, threashold=0.5):
'''
Transform numeric data into binarize 0 and 1
>>> y = np.array([0.27, 0.07, 0.56, 0.35, 0.32, 0.65])
>>> binarize(y)
array([0, 0, 1, 0, 0, 1])
'''
return np.where(np.array(y) < threashold, 0, 1)
def sigmoid(x):
'''
Batch version Sigmoid function
>>> x = np.array([-0.27, 0.07, 0.56, -0.35, 0.32, -0.65])
>>> sigmoid(x)
array([0.4329071 , 0.51749286, 0.63645254, 0.41338242, 0.57932425,
0.34298954])
'''
return 1 / (1 + np.exp(-x))
def relu(x):
'''
Batch version ReLU function
>>> x = np.array([-0.27, 0.07, 0.56, -0.35, 0.32, -0.65])
>>> relu(x)
array([0. , 0.07, 0.56, 0. , 0.32, 0. ])
'''
return np.where(x<0, 0, x)
class My3LPClassifier:
'''An naive 3 layer perceptron classifier'''
def __init__(self, layer_dims):
assert len(layer_dims)==3, 'layer_dims should be 3 elements'
self.input_size = layer_dims[0]
self.hidden_size = layer_dims[1]
self.output_size = layer_dims[-1]
self.V = np.random.randn(self.input_size, self.hidden_size)
self.gamma = np.zeros([1, self.hidden_size])
self.W = np.random.randn(self.hidden_size, self.output_size)
self.theta = np.zeros([1, self.output_size])
def _forward(self, x):
'''forward computing'''
x = x.reshape([1,-1])
alpha = np.dot(x, self.V) - self.gamma
b = relu(alpha)
# b = sigmoid(alpha)
beta = np.dot(b, self.W)
y_hat = sigmoid(beta) - self.theta
return alpha, b, beta, y_hat
def _loss(self, y, y_hat):
'''mean square error'''
return np.sum(np.square(y-y_hat)) / len(y)
def _back(self, x, y, alpha, b, beta, y_hat, eta):
'''back propagation'''
g = y_hat * (1-y_hat) * (y - y_hat)
self.W = self.W + eta * (g * b.reshape([-1,1]))
self.theta = self.theta - eta * g
e = np.where(alpha<0, 0, alpha) * np.sum(self.W * g, axis=1)
# e = b * (1-b) * np.sum(self.W * g, axis=1)
self.V = self.V + eta * (e * x.reshape([-1,1]))
self.gamma = self.gamma - eta * e
def fit(self, X, y, eta=1e-5, eps=1e-6, max_iter=10000):
'''
Build an random forest classifier
Parameters
----------
X: ndarray of shape (m, n)
sample data where row represent sample and column represent feature
y: ndarray of shape (m, k)
labels of sample data
Returns
-------
self
trained model
'''
self.X = X
self.y = y
i = 0
prev_loss = np.inf
while i < max_iter:
predicted = []
for x, y in zip(self.X, self.y):
alpha, b, beta, y_hat = self._forward(x)
self._back(x, y, alpha, b, beta, y_hat, eta)
predicted.append(y_hat)
this_loss = self._loss(self.y, np.array(predicted))
if prev_loss - this_loss < eps:
break
prev_loss = this_loss
i += 1
return self
def predict(self, X):
'''
Make prediction by the trained model.
Parameters
----------
X: ndarray of shape (m, n)
data to be predicted, the same shape as trainning data
Returns
-------
C: ndarray of shape (m, k)
Predicted class label(s) per sample.
'''
y = []
for x in X:
y.append(self._forward(x)[-1])
return binarize(np.array(y))
測(cè)試一下
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
print('\nMultilayer Perceptron')
print('---------------------------------------------------------------------')
X, y = make_classification(n_samples=1000, n_features=4,
n_informative=2, n_redundant=0,
random_state=0, shuffle=False)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
mymlp = My3LPClassifier(layer_dims=[X_train.shape[1], 3, 1])
mymlp.fit(X_train, y_train)
print('My 3LP:', accuracy_score(mymlp.predict(X_test).flatten(), y_test))
from sklearn.neural_network import MLPClassifier
skmlp = MLPClassifier()
skmlp.fit(X_train, y_train)
print('Sk 3LP:', accuracy_score(skmlp.predict(X_test), y_test))
測(cè)試結(jié)果如下,可謂慘不忍睹,scikit-learn里面任何一個(gè)分類器拿出來都能吊打,側(cè)面反映出網(wǎng)絡(luò)的優(yōu)化是多么重要。
Multilayer Perceptron
---------------------------------------------------------------------
My 3LP: 0.76
Sk 3LP: 0.96
更多代碼請(qǐng)參考https://github.com/qige96/programming-practice/tree/master/machine-learning
本作品首發(fā)于簡(jiǎn)書 和 博客園平臺(tái),采用知識(shí)共享署名 4.0 國(guó)際許可協(xié)議進(jìn)行許可。