自己動手,編寫神經網絡程序,解決Mnist問題,並網絡化部署-編寫網絡


基於《神經網絡和深度學習》這本絕好的教材提供的相關資料和代碼,我們自己動手編寫“隨機取樣的梯度下降神經網絡”。為了更好地說明問題,我們先從簡單的開始:

1、sigmod函數,基本上就是基於定義的;
 
#########helper函數########
#計算sigmoid,這個函數來自定義
def sigmoid( z):
return 1.0/( 1.0+np.exp(-z))
#計算sigmoid的導數,這個函數可以被證明
def sigmoid_prime( z):
return sigmoid(z)*( 1 - sigmoid(z))
 
2、構造函數
###########Main函數########
#使用例子 net = GoNetwork([2, 3, 1])
class GoNetwork( object):

def __init__( self, sizes): #構造函數
self.num_layers = len(sizes) #層數
self.sizes = sizes #每層size
#隨機生成子節點
self.biases= [np.random.randn(y, 1) for y in sizes[ 1:]]
# net.weights[1] 是一個存儲着連接第二層和第三層神經元權重的 Numpy 矩陣。
self.weights = [np.random.randn(y, x)
for x, y in zip(sizes[:- 1], sizes[ 1:])]
這個地方有以下幾個地方,一個是在Python中類和類的構造函數是這樣定義的;二個是Python如何體現出其強大的數據處理能力的。
這里,如果
sizes = [2,  3,  1]
則sizes [1:] = [3,1]
 
numpy.random.randn(d0, d1, ..., dn)

這個函數的作用就是從標准正態分布中返回一個或多個樣本值,比如

bbb = [np.random.randn( 3, 2)]
表示的是生成3X2的隨機序列,可以這樣來使用,就是加上偏置了
2.5 * np.random.randn(2, 4) + 3

返回:

array([[ 4.128****53,  1.764****44 ,  2.732****92,  2.90839231],
      [ 0.174****86,   4.92026887,  1.574****66, -0.4305991 ]])
aaa =[ np.random.randn(y, 1) for y in sizes[ 1:]]
這是一種Python的連寫方法,這里就是對[3,1]分別生成隨機序列。這個隨機是用來干什么的?就是隨機的權值。
描述 zip() 函數用於將可迭代的對象作為參數,將對象中對應的元素打包成一個個元組,然后返回由這些元組組成的列表
這里
zip(sizes[:-1], sizes[1:])
表示的是將第1、2層之間,2、3層之間的全連接生成隨機權值。
 
3、前向網絡,主要用於測試當前網絡
def feedforward( self, a):
for b,w in zip( self.biases, self.weights):
a = sigmoid(np.dot(w,a)+b)
return a
非常直接的按照定義,進行上一層到下一層的前向 計算,注意這里得到的a也是x行1列的一個矩陣
 
4、評價函數,基本上也是按照定義進行設定的
def evaluate( self, test_data):
test_results = [(np.argmax( self.feedforward(x)), y) #這里需要注意feedforward的參數x,實際上它是一個in/out參數。
for (x, y) in test_data]
return sum( int(x == y) for (x, y) in test_results) #做出了正確的預測
這個地方調用了feedforward(x),並且和y進行比較,得到准確比對有哪些。應該說代碼非常精簡。
 
5、代價函數
#cost代價函數
def cost_derivative( self, output_activations, y):
return (output_activations-y)
 
以上幾項都是非常好理解的,基本上你看到的立刻就能夠理解,需要補充的知識並不是很多。結合上一課的相關知識,我們這里提出的所謂隨機,就是提取很小的一塊數據,而后進行計算梯度下降參數,更新網絡的權重和偏置
def update_mini_batch( self, mini_batch, eta):
nabla_b = [np.zeros(b.shape) for b in self.biases] #生成b和w形狀的以0填充的矩陣
nabla_w = [np.zeros(w.shape) for w in self.weights]
for x, y in mini_batch:
delta_nabla_b, delta_nabla_w = self.backprop(x, y) #理解反向傳播就是一種快速計算梯度的方法
nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
self.weights = [w-(eta/ len(mini_batch))*nw
for w, nw in zip( self.weights, nabla_w)]
self.biases = [b-(eta/ len(mini_batch))*nb
for b, nb in zip( self.biases, nabla_b)]
其中
   nabla_b = [np.zeros(b.shape) for b in self.biases]
   nabla_w = [np.zeros(w.shape) for w in self.weights]
生成b和w形狀的以0填充的矩陣,這里就是用來填充原始數據的。
在這個小循環里面,我們可以以“黑箱”的形式來理解backprop函數,就是一種用來計算最快下降梯度的方法。
 for x, y in mini_batch:
            delta_nabla_b, delta_nabla_w = self.backprop(x, y)
            nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
            nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
在這里,我們便歷所有的mini_batch,注意在上面這行代碼中,
而后,引入eta,以這個梯度作為 delta_nabla_b,  delta_nabla_w  的初始值都為空.
這樣,我們按照定義進行了一次小數據的更新。其能夠完成,是因為backprop為我們成功計算了代價函數的兩個梯度。
6、后向傳播函數,其目的是進行梯度下降計算,是最為復雜的部分
#反向傳播就是一種快速計算代價函數梯度的方法,也就是計算delta的一種方法
def backprop( self, x, y):
#都以空矩陣來進行初始化
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
# feedforward
activation = x
activations = [x] # list to store all the activations, layer by layer
zs = [] # list to store all the z vectors, layer by layer
for b, w in zip( self.biases, self.weights):
z = np.dot(w, activation)+b #前向傳播
zs.append(z)
activation = sigmoid(z)
activations.append(activation)
# backward pass
delta = self.cost_derivative(activations[- 1], y) * \
sigmoid_prime(zs[- 1])
nabla_b[- 1] = delta
nabla_w[- 1] = np.dot(delta, activations[- 2].transpose())
 
for l in range( 2, self.num_layers):
z = zs[-l]
sp = sigmoid_prime(z)
delta = np.dot( self.weights[-l+ 1].transpose(), delta) * sp
nabla_b[-l] = delta
nabla_w[-l] = np.dot(delta, activations[-l- 1].transpose())
return (nabla_b, nabla_w)
 
其中內容比較復雜,一條一條進行解釋 
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
生成空矩陣
# feedforward
activation = x
activations = [x] # list to store all the activations, layer by layer
zs = [] # list to store all the z vectors, layer by layer
for b, w in zip( self.biases, self.weights):
z = np.dot(w, activation)+b #前向傳播
zs.append(z)
activation = sigmoid(z)
activations.append(activation)
 
前向計算,保存所有b、w和 z。后面的幾行代碼,主要都是和4個公式嚴格對應的
delta = self.cost_derivative(activations[- 1], y) * sigmoid_prime(zs[- 1])
 
對應BP1
 
nabla_b[- 1] = delta
nabla_w[- 1] = np.dot(delta, activations[- 2].transpose())
 
分別對應BP3和BP4,就是最后來計算具體的梯度值
 
delta = np.dot( self.weights[-l+ 1].transpose(), delta) * sp
 
對應BP2,反向計算。
 
7、 隨機梯度下降算法,到了這里也就是將上面的合起來
#隨機梯度下降算法
def SGD( self, training_data, epochs, mini_batch_size, eta, test_data= None):

training_data = list(training_data)
n = len(training_data)

if test_data:
test_data = list(test_data)
n_test = len(test_data)
#⾸先隨機地將訓練數據打亂
for j in range(epochs):
random.shuffle(training_data)
#再將它分成多個適當⼤⼩的⼩批量數據
mini_batches = [
training_data[k:k+mini_batch_size]
for k in range( 0, n, mini_batch_size)]
for mini_batch in mini_batches: #最主要的一行代碼
self.update_mini_batch(mini_batch, eta)
if test_data:
print( "Epoch {} : {} / {} ".format(j, self.evaluate(test_data),n_test))
else:
print( "Epoch {} complete".format(j))
主要優化的地方,就是將原較大的數據集分成多個部分,而后遍歷所有的部分,進行梯度下降運算,並且打印比較的結果。應該說再次體現了Python強大的集成編碼能力。
 






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM