1.神經(jīng)網(wǎng)絡(luò)前傳——感知機(jī)
感知機(jī)作為神經(jīng)網(wǎng)絡(luò)的起源,由Frank Rosenblatt(美國)在1957 年提出。感知機(jī)的構(gòu)造與神經(jīng)網(wǎng)絡(luò)中的神經(jīng)元類似,理解感知機(jī)的思想對后續(xù)了解神經(jīng)網(wǎng)絡(luò)有很重要的幫助。
感知機(jī)的邏輯形狀類似于生物的神經(jīng)系統(tǒng),由神經(jīng)元與神經(jīng)元之間連接構(gòu)成。感知機(jī)的節(jié)點(diǎn)包含激活函數(shù),用來處理節(jié)點(diǎn)收到的信息(激活函數(shù)詳見附錄),類似于神經(jīng)系統(tǒng)的神經(jīng)元;節(jié)點(diǎn)與節(jié)點(diǎn)之間由邊連接,邊包含權(quán)重等信息,類似于神經(jīng)系統(tǒng)中神經(jīng)細(xì)胞的突觸。具體類比如下圖:


每個(gè)節(jié)點(diǎn)接收到連接他的相鄰節(jié)點(diǎn)傳來的信息作為輸入,在上圖中神經(jīng)元B接受來自于A1、A2的輸入x1、x2,在輸入信號(hào)分別乘以對應(yīng)邊上的權(quán)重后求和,如果總和超過某個(gè)閾值,則該神經(jīng)元被激活。這里將這個(gè)閾值用表示。感知機(jī)的數(shù)學(xué)公式如下:
NOTE:
感知機(jī)的輸入都有各自的權(quán)重,權(quán)重越大,意味著這個(gè)輸入對該神經(jīng)元的狀態(tài)影響越大。而后面的神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí),就是在不斷調(diào)優(yōu)尋求最佳權(quán)重的過程。
2.用感知機(jī)思想實(shí)現(xiàn)邏輯電路
我們來回顧一下離散數(shù)學(xué)。這里用感知機(jī)的思想去實(shí)現(xiàn)與門、或門、與非門、異或門是為了更切實(shí)的體驗(yàn)感知機(jī)的思想,并形象地展現(xiàn)權(quán)重和偏置在神經(jīng)網(wǎng)絡(luò)中作用的作用,最后我們會(huì)引入激活函數(shù)的概念,并在下一章“神經(jīng)網(wǎng)絡(luò)”中進(jìn)行詳細(xì)介紹。
2.1與門
與門真值表
| x1 | x2 | y |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
如果我們用代碼實(shí)現(xiàn)邏輯電路與門、或門、與非門、異或門異常簡單。但此處我們引入權(quán)重與偏置等概念,使用感知機(jī)的思想實(shí)現(xiàn)與門、或門、與非門、異或門。
我們給x1、x2分別乘以各自邊上的權(quán)重再求和,最后加上偏置量等于輸出y。
與門代碼實(shí)現(xiàn):
import numpy as np
def AND(x1, x2):
x = np.array([x1, x2])
w = np.array([0.5, 0.5])
b = -0.7
tmp = np.sum(w*x) + b
if tmp <= 0:
return 0
else:
return 1
2.2或門
或門真值表
| x1 | x2 | y |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
或門代碼實(shí)現(xiàn):
import numpy as np
def OR(x1, x2):
x = np.array([x1, x2])
w = np.array([0.5, 0.5])
b = -0.2
tmp = np.sum(w*x) + b
if tmp <= 0:
return 0
else:
return 1
2.3與非門
與非門真值表
| x1 | x2 | y |
|---|---|---|
| 0 | 0 | 1 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
與非門代碼實(shí)現(xiàn):
import numpy as np
def NAND(x1, x2):
x = np.array([x1, x2])
w = np.array([-0.5, -0.5])
b = 0.7
tmp = np.sum(w*x) + b
if tmp <= 0:
return 0
else:
return 1
2.3異或門
異或門真值表
| x1 | x2 | y |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
異或門是比較特殊的邏輯門,因?yàn)橛^察異或門的真值表可以發(fā)現(xiàn),他的true值分布是非線性的。在前面三個(gè)邏輯門中,如果以x1為橫軸,以x2為縱軸,true值和false值可以用一條直線來區(qū)分開來,以或門為例:
分割true值與false值的線性函數(shù)為:
然而異或門的真值不是以上述這種方式分布,所以用單層感知機(jī)無法實(shí)現(xiàn)異或門。我們在離散數(shù)學(xué)中都學(xué)到過異或門可以通過與門、或門以及非與門組合實(shí)現(xiàn),因此我們使用多層感知機(jī)來實(shí)現(xiàn)異或門。
與門代碼實(shí)現(xiàn):
def XOR(x1, x2):
s1 = NAND(x1, x2)
s2 = OR(x1, x2)
y = AND(s1, s2)
return y
此處多層感知機(jī)就是神經(jīng)網(wǎng)絡(luò)的雛形,每個(gè)神經(jīng)元的輸入都有各自不同的權(quán)重,每個(gè)神經(jīng)元都有自己的激活函數(shù)以及偏置量。最終的輸出神經(jīng)元就是所要的結(jié)果。
2.神經(jīng)網(wǎng)絡(luò)
我們已經(jīng)了解了感知機(jī)的基本概念與結(jié)構(gòu),感知機(jī)既有優(yōu)點(diǎn)又有缺點(diǎn)。優(yōu)點(diǎn)是感知機(jī)理解起來簡單容易,在遇到復(fù)雜問題上,感知機(jī)理論上是可以使用并解決問題的。但感知機(jī)最最致命的缺點(diǎn)就是所有的權(quán)重需要人工確認(rèn),例如我們實(shí)現(xiàn)邏輯門時(shí),權(quán)重是由人為確定。在處理簡單問題時(shí),人工參與確認(rèn)權(quán)重可行,一旦問題復(fù)雜,所用的感知機(jī)神經(jīng)元數(shù)量龐大,結(jié)構(gòu)復(fù)雜時(shí),人工確認(rèn)權(quán)重就變得困難。因此我們進(jìn)一步探索神經(jīng)網(wǎng)絡(luò),神經(jīng)網(wǎng)絡(luò)提供了一套機(jī)器自己確定權(quán)重的方法(但并非所有參數(shù)都可以自己自動(dòng)決定,神經(jīng)網(wǎng)絡(luò)中一些超參數(shù)依舊需要人工確定。超參數(shù)將會(huì)在后續(xù)分享中提及)
這一節(jié)將大致介紹神經(jīng)網(wǎng)絡(luò)。這里我們默認(rèn)神經(jīng)網(wǎng)絡(luò)的權(quán)重已經(jīng)獲取到合適的值。我們僅僅使用神經(jīng)網(wǎng)絡(luò)進(jìn)行分類。神經(jīng)網(wǎng)絡(luò)每個(gè)神經(jīng)元的權(quán)重學(xué)習(xí)將在下一節(jié)分享中詳述。
2.1神經(jīng)網(wǎng)絡(luò)圖解
此處我們構(gòu)建一個(gè)三層神經(jīng)網(wǎng)絡(luò),用來說明神經(jīng)網(wǎng)絡(luò)的基本結(jié)構(gòu)。三層神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)如下圖:

神經(jīng)網(wǎng)絡(luò)說明:
層說明:上圖展示的神經(jīng)網(wǎng)絡(luò)為一個(gè)三層的神經(jīng)網(wǎng)絡(luò)(實(shí)際上有4層,但是第一層是從0開始算起,因此稱為三層神經(jīng)網(wǎng)絡(luò))圖中由x1,x2組成的神經(jīng)網(wǎng)絡(luò)層是第0層,稱作輸入層,由y1,y2組成的神經(jīng)網(wǎng)絡(luò)層是第3層,稱作輸出層。夾在中間兩層的是隱藏層。
參數(shù)說明:邊上的w為權(quán)重,b為每層的偏置量。w是由參數(shù)組成的二維數(shù)組。b是由常數(shù)組成的數(shù)組,也就是說每一個(gè)神經(jīng)元接收到的偏置量可以不一樣。
函數(shù)說明:h(x)為激活函數(shù),激活函數(shù)詳見 附錄 零食一:激活函數(shù)。
2.2神經(jīng)網(wǎng)絡(luò)的運(yùn)算
總體計(jì)算流程:輸入層的神經(jīng)元的輸入值乘以每條邊上的權(quán)重,然后傳遞給下一個(gè)神經(jīng)元,在下一個(gè)神經(jīng)元上將各個(gè)輸入以及偏置量b求和,總和為a。z = h(a),h()為激活函數(shù)。z作為本神經(jīng)元的輸出傳遞給下一個(gè)神經(jīng)元。
- 輸入:輸入層是一系列輸入神經(jīng)元,我們可以將輸入作為一個(gè)一維數(shù)組來看待。后續(xù)批處理的時(shí)候輸入是分批輸入,輸入可以抽象為二元數(shù)組。
- 權(quán)重:權(quán)重可以抽象為二維數(shù)組,權(quán)重二維數(shù)組的第一維的形狀必須和輸入層(上一層的輸出)的第二維的形狀相同。
- 中間結(jié)果:中間結(jié)果是由上一層的輸入乘以權(quán)重加上偏置計(jì)算得出的。可以抽象為一個(gè)一維數(shù)組(在批量處理中是二維數(shù)組)具體計(jì)算參見 附錄 零食二:矩陣乘法
- 輸出結(jié)果:輸出結(jié)果由一系列輸出層的神經(jīng)元組成。根據(jù)不同目標(biāo)會(huì)有不同個(gè)數(shù)的神經(jīng)元。例如我們在手寫數(shù)字分類的神經(jīng)網(wǎng)絡(luò)中,輸出元有10個(gè),分別代表0~9的概率。
2.3三層神經(jīng)網(wǎng)絡(luò)的代碼實(shí)現(xiàn)
此處我們將權(quán)重的學(xué)習(xí)跳過,將權(quán)重信息寫死在程序當(dāng)中。權(quán)重的學(xué)習(xí)將放置在后續(xù)的分享當(dāng)中。
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def identity_function(x):
return x
def init_network():
network = {}
network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
network['b1'] = np.array([0.1, 0.2, 0.3])
network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
network['b2'] = np.array([0.1, 0.2])
network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]])
network['b3'] = np.array([0.1, 0.2])
return network
def forward(network, x):
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, W3) + b3
y = identity_function(a3)
return y
network = init_network()
x = np.array([1.0, 0.5])
y = forward(network, x)
print(y)
2.4輸出層的設(shè)計(jì)
在上面的代碼實(shí)現(xiàn)中我們發(fā)現(xiàn)我們最后使用的identity_function是簡單的只返回輸入x,相當(dāng)于什么也沒做,這種函數(shù)叫做恒等函數(shù)。這一小節(jié)我們討論下輸出層的設(shè)計(jì)。
神經(jīng)網(wǎng)絡(luò)可以用在分類問題與回歸問題上,需要根據(jù)不同情況來使用不同的激活函數(shù),一般而言,回歸問題用恒等函數(shù),分類問題用softmax函數(shù)。
2.4.1softmax函數(shù)
softmax函數(shù)的數(shù)學(xué)表示如下:
說明:假設(shè)輸出層有N個(gè)神經(jīng)元,在第k個(gè)神經(jīng)元輸出yk,因?yàn)榉诸悊栴}需要的是每個(gè)分類的概率,softmax是一個(gè)大于0小于1的分?jǐn)?shù),softmax輸出值的總和為1,所以使用softmax作為輸出層的激活函數(shù)來處理分類問題。
2.4.2softmax函數(shù)的實(shí)現(xiàn)
import numpy as np
def softmax(x):
exp_x = np.exp(x)
sum_exp_x = np.sum(exp_x)
y = exp_x / sum_exp_x
return y
a = np.array([0.3, 2.9, 4.0])
print(softmax(a))
2.4.3softmax中的陷阱
我們來回憶一下的圖像,當(dāng)x趨于0時(shí),y值趨于負(fù)無窮。這種情況下計(jì)算機(jī)會(huì)溢出。如果我們分類函數(shù)用如上的softmax作為輸出層的激活函數(shù),遇到一個(gè)概率判斷很小的分類,就爆炸了。。。那如何避免這個(gè)數(shù)學(xué)上的陷阱呢?解決方案非常粗暴簡單,給分子、分母加個(gè)不為0的常數(shù)。改進(jìn)后的softmax的數(shù)學(xué)表示如下:
說明:分子、分母同時(shí)乘以一個(gè)不為零的常數(shù),運(yùn)算結(jié)果不會(huì)改變。經(jīng)過如上變換,只需要給輸出加上一個(gè)不為零的常數(shù)即可。為了防止計(jì)算機(jī)溢出。我們減去輸出層的神經(jīng)元最大的輸出項(xiàng)作為C'
2.4.4改進(jìn)后的softmax代碼實(shí)現(xiàn)
import numpy as np
def softmax(x):
c = np.max(x)
exp_x = np.exp(x - c)
sum_exp_x = np.sum(exp_x)
y = exp_x / sum_exp_x
return y
a = np.array([0.3, 2.9, 4.0])
print(softmax(a))
附錄:
附錄附上一些在感知機(jī)中可能需要了解的數(shù)學(xué)知識(shí)。如果你的大學(xué)數(shù)學(xué)記憶猶新可以不看這部分附錄。如果你和我一樣大學(xué)數(shù)學(xué)全部還給老師的話,可以在此復(fù)習(xí)一下。別怕!所需要的高數(shù)知識(shí)非常簡單。
零食一:激活函數(shù)
將輸入信號(hào)(多于一個(gè))進(jìn)行一番運(yùn)算轉(zhuǎn)換為輸出信號(hào),這種函數(shù)統(tǒng)稱為激活函數(shù)。我們來看幾個(gè)常用激活函數(shù)的例子:
1.階躍函數(shù)
NOTE:
w:邊上的權(quán)重
b:偏置量
說明:
此神經(jīng)元接受兩個(gè)輸入信號(hào),x1,x2,兩個(gè)輸入信號(hào)分別乘以兩條邊上的權(quán)重w1,w1求和再加上偏置量b作為最終階躍函數(shù)判斷的依據(jù)。如果和小于0則階躍函數(shù)輸出0,如果和大于0則階躍函數(shù)輸出1。由于最終輸出為0或1,在平面直角坐標(biāo)系中的圖像像臺(tái)階一樣,因此稱之為階躍函數(shù)。
階躍函數(shù)的代碼實(shí)現(xiàn):
import numpy as np
import matplotlib.pylab as plt
def step_function(x):
return np.array(x > 0, dtype=np.int)
X = np.arange(-5.0, 5.0, 0.1)
Y = step_function(X)
plt.plot(X, Y)
plt.ylim(-0.1, 1.1)
plt.show()
階躍函數(shù)圖像:

2.SIGMOD函數(shù)
SIGMOD函數(shù)是神經(jīng)網(wǎng)絡(luò)中較為常用的一個(gè)激活函數(shù),SIGMOD函數(shù)的數(shù)學(xué)表示如下:
NOTE:
w:邊上的權(quán)重
b:偏置量
相對于階躍函數(shù),SIGMOD函數(shù)曲線更加平滑,而且范圍也是在(0,1)之間,SIGMOD函數(shù)在神經(jīng)網(wǎng)絡(luò)之中有很特殊的意義。在某些場景下SIGMOD函數(shù)的輸出更符合需求,例如我們需要求各個(gè)輸出神經(jīng)元對應(yīng)的概率。在這種情況下,我們需要的是一個(gè)0到1之間的實(shí)數(shù),而非0/1的二元輸出。
SIGMOD函數(shù)的代碼實(shí)現(xiàn):
import numpy as np
import matplotlib.pylab as plt
def sigmoid(x):
return 1 / (1 + np.exp(-x))
X = np.arange(-5.0, 5.0, 0.1)
Y = sigmoid(X)
plt.plot(X, Y)
plt.ylim(-0.1, 1.1)
plt.show()
SIGMOD函數(shù)的圖像:

3.ReLU函數(shù)
在激活函數(shù)的發(fā)展史中SIGMOD函數(shù)很早就開始使用了。但是近期則主要使用ReLU函數(shù)。(Rectified Linear Unit)
ReLU函數(shù)的數(shù)學(xué)表示如下:
ReLU函數(shù)的代碼實(shí)現(xiàn):
import numpy as np
import matplotlib.pylab as plt
def relu(x):
return np.maximum(0, x)
x = np.arange(-5.0, 5.0, 0.1)
y = relu(x)
plt.plot(x, y)
plt.ylim(-1.0, 5.5)
plt.show()
ReLU函數(shù)的圖像:

零食二:矩陣乘法
理論說的再好不如例子一個(gè),我們舉個(gè)來說明矩陣乘法怎么算。
我們定義二維矩陣A, B
A = {[2,4],[2,6],[5,6]}
矩陣A的形狀為3×2
B = {[1,2,3],[4,5,6]}
矩陣A的形狀為2×3
說明:矩陣的形狀被乘數(shù)為第一維元素的個(gè)數(shù),乘數(shù)為第二維元素的個(gè)數(shù)。如果兩個(gè)二維矩陣相乘,必須保證第一個(gè)矩陣的第二維形狀與第二個(gè)矩陣第一維的形狀相等。在上述例子中為2 = 2,如果不相等則矩陣不可乘。
矩陣A乘以B記做A·B,我們用矩陣S表示(S = A·B)。S的形狀為第一個(gè)矩陣的第一維的形狀×第二個(gè)矩陣的第二維的形狀,即shape(S) = 3×3,具體運(yùn)算規(guī)則如下:
S[0][0] = A[0][0]×B[0][0] + A[0][1]×B[1][0] = 2×1 + 4×4 = 17
S[0][1] = A[0][0]×B[0][1] + A[0][1]×B[1][1] = 2×2 + 4×5 = 24
S[0][2] = A[0][0]×B[0][2] + A[0][1]×B[1][2] = 2×3 + 4×6 = 30
S[1][0] = A[1][0]×B[0][0] + A[1][1]×B[1][0] = 2×1 + 6×4 = 26
……
S[2][2] = A[2][0]×B[0][2] + A[2][1]×B[1][2] = 5×3 + 6×6 = 51
最終結(jié)果為:
S = {[17,24,30],[26,34,42],[29,40,51]}
Reference:
[1]齋藤康毅., 2016, Deep Learning from Scratch, Tokyo: O'Reilly Japan, Inc.
[2]耿素云.,1998, 離散數(shù)學(xué).第2分冊,集合論與圖論, 北京: 北京大學(xué)出版社.
[3]李響初., 2012, 數(shù)字電路基礎(chǔ)與應(yīng)用, 北京: 機(jī)械工業(yè)出版社.