網(wǎng)絡(luò)構(gòu)成

我們的大腦接收眼睛觀察傳播來的數(shù)據(jù)后,會對其進(jìn)行一層層的神經(jīng)元去解析數(shù)據(jù),然后得到我們對于所見的判斷。然而我們對這個分析過程的了解以及腦部的研究較為淺,無法得知其腦部工作的原理。但是,我們是否可以對其進(jìn)行部分抽象化為以下過程:

腦部在接收一系列數(shù)據(jù)的時候進(jìn)行了一個函數(shù)式(function)的抽象,得到了其認(rèn)知。
那么,我們的神經(jīng)網(wǎng)絡(luò)就是模擬這個過程,將輸入信號,神經(jīng)元的function處理,以及輸出,都數(shù)字化。用統(tǒng)計與學(xué)習(xí)的方式,將這個過程一步步還原;

其全連接神經(jīng)網(wǎng)絡(luò)規(guī)則如下:
- 神經(jīng)元按照層來布局。最左邊的層叫做輸入層,負(fù)責(zé)接收輸入數(shù)據(jù);最右邊的層叫輸出層,我們可以從這層獲取神經(jīng)網(wǎng)絡(luò)輸出數(shù)據(jù)。輸入層和輸出層之間的層叫做隱藏層,因為它們對于外部來說是不可見的。
- 同一層的神經(jīng)元之間沒有連接。
- 第N層的每個神經(jīng)元和第N-1層的所有神經(jīng)元相連(這就是full connected的含義),第N-1層神經(jīng)元的輸出就是第N層神經(jīng)元的輸入。
- 每個連接都有一個權(quán)值。
神經(jīng)元
神經(jīng)元是構(gòu)成神經(jīng)網(wǎng)絡(luò)的基本單位:

一個神經(jīng)元的組成為:
- 輸 入:n維度向量x
-
線性加權(quán):
- 激活函數(shù): $H(x)$,要求非線性,容易求導(dǎo)數(shù)
- 輸出 a
激活函數(shù)的選擇
(1)sigmoid函數(shù)

(2)tanh函數(shù)

(3)relu函數(shù)

計算樣例
下面我們來用最簡單是sigmoid函數(shù)來嘗試手算一個很簡單的神經(jīng)網(wǎng)絡(luò):

- 中間藍(lán)色的神經(jīng)元代表了隱層神經(jīng)元,其左邊的是W權(quán)值,b代表了其偏置項。
- X 為輸入層
- 紅色神經(jīng)元代表輸出,只有一個神經(jīng)元代表這個神經(jīng)網(wǎng)絡(luò)只有一個輸出
x1與z1的連線權(quán)值為$0.1$,x1的輸出為$0.5$,所以x1到z1的輸入為$0.1$*$0.5$,同理。x2到z1的輸入為$-0.06$,所以根據(jù)計算公式,z1的輸入為:$0.1 0.5 +0.2-(0.3)+0.01$,再將其進(jìn)行激活函數(shù)處理,得到Z1的輸出:$0.50224$
神經(jīng)網(wǎng)絡(luò)的訓(xùn)練
- 我們需要知道一個神經(jīng)網(wǎng)絡(luò)的每個連接上的權(quán)值.
- 我們可以說神經(jīng)網(wǎng)絡(luò)就是一個模型,這些權(quán)值就是模型的參數(shù)(即模型要學(xué)習(xí)的東西),
- 對于這個神經(jīng)網(wǎng)絡(luò)的連接方式,網(wǎng)絡(luò)層數(shù),每層的節(jié)點(diǎn)數(shù),這些,我們是事先設(shè)置的,成為超參數(shù).
目標(biāo)函數(shù)與誤差計算
在監(jiān)督學(xué)習(xí)中,對于每一個樣本,我們得到其特征$x$,標(biāo)記$y$.我們通過模型$h(x)$計算得到輸出值:
,顯然$y$是其真實值,

數(shù)學(xué)中常用的辦法是將兩個值的差的平方的$1/2$來代表其接近程度:

我們將$e$當(dāng)做單個成本的誤差,將所有的訓(xùn)練樣本的誤差值相加,得到其誤差誤差項$E$:

訓(xùn)練的目標(biāo),就是找到合適的$w$,使誤差$E$最小;
訓(xùn)練方法:反向傳播算法(Back Propagation)
簡單的說,反向傳播算法的過程就是:
- 給定的一個輸入集$X$,取其中的一個向量$x$
- 先正向計算$x$的神經(jīng)網(wǎng)絡(luò),得到其輸出值$y$
- 訓(xùn)練向量$x$有人為標(biāo)定的標(biāo)簽$l$(label),計算其預(yù)測值跟真實值之間的誤差,
- 將誤差項每一層地反向傳播,得到每一層的誤差,然后利用每一層的誤差去更新每一層的權(quán)值去擬合模型
我們先給出各層的計算公式,然后再進(jìn)行數(shù)學(xué)公式的推導(dǎo)(不擅長數(shù)學(xué)的可以跳過,等代碼實現(xiàn)再回來看推導(dǎo))
(1) 輸出層的誤差計算:

其中,

(2) 隱藏層的誤差計算與傳遞

我們知道:

更新權(quán)重:

數(shù)學(xué)公式的推導(dǎo):
我們?nèi)【W(wǎng)絡(luò)所有輸出層節(jié)點(diǎn)的誤差平方和作為目標(biāo)函數(shù):

,
用隨機(jī)梯度下降算法對目標(biāo)函數(shù)進(jìn)行優(yōu)化:


我們可以從上圖得到,$W84$僅影響$a4$節(jié)點(diǎn)到$8$節(jié)點(diǎn)的輸入值,設(shè):$netj$為節(jié)點(diǎn)$j$的加權(quán)輸入:

$Ed$是$netj$的函數(shù),而$netj$是$Wji$的函數(shù),根據(jù)鏈?zhǔn)角髮?dǎo)法則:

$xji$是節(jié)點(diǎn)i到節(jié)點(diǎn)j的輸出值,即$j$的輸入值
輸出層:
netj僅影響節(jié)點(diǎn)j的輸出值,,所以Ed是yi的函數(shù),yi是netj的函數(shù):

計算第一項
將Ed帶入公式,得到:

計算第二項:

綜合,得:

將其推導(dǎo)代入 隨機(jī)推導(dǎo)公式,得:

隱層:
我們定義節(jié)點(diǎn)j的所有直接下游節(jié)點(diǎn)的集合為:$Downstream(j)$,所以netj只能通過影響$Downstream(j)$來影響$Ed$,設(shè)Netk是節(jié)點(diǎn)J的下游輸入,而$Ed$是netk的函數(shù),而netk是netj的函數(shù),所以我們用全導(dǎo)數(shù)公式:

因為:


自此,我們完成了公式的推導(dǎo)
代碼:
import numpy as np
#定義tan函數(shù)以及tan函數(shù)的導(dǎo)數(shù)
def tanh(x):
return np.tanh(x)
def tanh_deriv(x):
return 1.0-np.tanh(x)*np.tanh(x)
#定義logistich函數(shù)以及其導(dǎo)數(shù)
def logistic(x):
return 1/(1+np.exp(-x))
def logistic_derivatrive(x):
return logistic(x)*(1-logistic(x))
class NeturalNetwork(object):
def __init__(self,layers,activations='tanh'):
'''
:param layers: 一個list 包括每一層的神經(jīng)元數(shù)
:param activations:激活函數(shù)
'''
if activations=='tanh':
self.activation = tanh
self.activation_deriv = tanh_deriv
if activations=='logistic':
self.activation = logistic
self.activation_deriv = logistic_derivatrive
self.weights = []
for i in range(1,len(layers)-1):
#i跟前一層的權(quán)重
self.weights.append((2*np.random.random((layers[i-1]+1,layers[i]+1))-1)*0.25)
#i層跟i+1層進(jìn)行賦值 權(quán)重
self.weights.append((2*np.random.random((layers[i]+1,layers[i+1]))-1)*0.25)
def fit(self,X,y,learning_rate = 0.2,epochs=10000):
'''
:param X:
:param y:
:param learning_rate: 學(xué)習(xí)率
:param epochs: 學(xué)習(xí)步驟
:return:
'''
#二維矩陣
X = np.atleast_2d(X)
#ones 矩陣全是1 shape函數(shù)返回的是行列數(shù)(返回一個List)跟X一樣維度的矩陣
temp = np.ones([X.shape[0],X.shape[1]+1])
#temp等于第一列到最后一列跟x一樣的矩陣
temp [:,0:-1]=X
X= temp
Y=np.array(y)
#第幾次循環(huán)
for k in range(epochs):
i = np.random.randint(X.shape[0])
#隨機(jī)取一個數(shù),代表第i行,對i行數(shù)據(jù)進(jìn)行更新
a = [X[i]]
#形成這一行數(shù)據(jù)為輸入的神經(jīng)網(wǎng)絡(luò),dot代表內(nèi)積
for l in range(len(self.weights)):
a.append(self.activation(np.dot(a[l],self.weights[l])))
#誤差
error = y[i]-a[-1]
deltas = [error * self.activation_deriv(a[-1])]
#開始往回算每一層的誤差
#deltas是所有權(quán)重的誤差列表
for l in range(len(a)-2,0,-1):
deltas.append(deltas[-1].dot(self.weights[l].T)*self.activation_deriv(a[l]))
deltas.reverse()
for i in range(len(self.weights)):
layers = np.atleast_2d(a[i])
delta = np.atleast_2d(deltas[i])
self.weights[i] += learning_rate*layers.T.dot(delta)
def predict(self,x):
x = np.array(x)
temp = np.ones(x.shape[0]+1)
temp[0:-1] = x
a =temp
for l in range(0,len(self.weights)):
a = self.activation(np.dot(a,self.weights[l]))
return a
用手寫訓(xùn)練集來測試我們的神經(jīng)網(wǎng)絡(luò):
import numpy as np
from sklearn.datasets import load_digits
from sklearn.metrics import confusion_matrix,classification_report
from sklearn.preprocessing import LabelBinarizer
from neuralNetwork.nn import NeturalNetwork
from sklearn.cross_validation import train_test_split
digits =load_digits()
#1797張 8*8的手寫數(shù)字圖片
X = digits.data
Y = digits.target
#標(biāo)準(zhǔn)化
X -=X.min()
X /=X.max()
nn = NeturalNetwork([64,100,10],'logistic')
#分離測試集跟訓(xùn)練集
X_train,X_test,y_train,y_test = train_test_split(X,Y)
labels_train = LabelBinarizer().fit_transform(y_train)
labels_test = LabelBinarizer().fit_transform(y_test)
print("start fitting")
nn.fit(X_train,labels_train,epochs=3000)
print("end training")
predictions=[]
for i in range(X_test.shape[0]):
o = nn.predict(X_test[i])
predictions.append(np.argmax(o))
#10*10矩陣(分類是10) ,對角線表示預(yù)測的對,行是預(yù)測值,列是真實值
print(confusion_matrix(y_test,predictions))
'''
v[[43 0 0 0 0 0 0 0 0 0]
[ 0 37 0 0 0 0 1 0 0 8]
[ 0 1 38 3 0 0 0 0 0 0]
[ 0 0 1 47 0 1 0 1 0 0]
[ 0 0 0 0 47 0 0 0 0 1]
[ 0 0 0 0 0 48 1 0 0 0]
[ 0 2 0 0 0 0 38 0 0 0]
[ 0 0 0 0 1 0 0 37 0 1]
[ 1 7 0 1 0 4 1 0 26 6]
[ 0 0 0 4 0 0 0 0 0 43]]
'''
print(classification_report(y_test,predictions))
'''統(tǒng)計
precision recall f1-score support
0 0.98 1.00 0.99 43
1 0.79 0.80 0.80 46
2 0.97 0.90 0.94 42
3 0.85 0.94 0.90 50
4 0.98 0.98 0.98 48
5 0.91 0.98 0.94 49
6 0.93 0.95 0.94 40
7 0.97 0.95 0.96 39
8 1.00 0.57 0.72 46
9 0.73 0.91 0.81 47
avg / total 0.91 0.90 0.90 450
'''
用向量式編程的思想改進(jìn)我們的代碼:
# -*- coding:utf-8 -*-
#!/usr/bin/local/bin/python
import numpy as np
class FullConnectedLayer(object):
def __init__(self,input_size,
output_size,
learing_rate,
activator):
self.input_size = input_size
self.output_size = output_size
self.activator = activator
self.learning_rate = learing_rate
self.W = np.random.uniform(-0.1,0.1,(output_size,input_size))
self.b = np.zeros((output_size,1))
self.output = np.zeros((output_size,1))
def forward(self,input_array):
self.input = input_array
self.output = self.activator.forward(
np.dot(self.W,input_array)+self.b
)
def backward(self,delta_array):
self.delta = self.activator.backward(self.input) * np.dot(
self.W.T,delta_array
)
self.W_grad = np.dot(delta_array,self.input.T)
self.b_grad = delta_array
def update(self):
self.W += self.learning_rate * self.W_grad
self.b += self.learning_rate * self.b_grad
