(Python)零起步数学+神经网络入门
在這篇文章中,我們將在Python中從頭開始了解用于構建具有各種層神經網絡(完全連接,卷積等)的小型庫中的機器學習和代碼。最終,我們將能夠寫出如下內容:
假設你對神經網絡已經有一定的了解,這篇文章的目的不是解釋為什么構建這些模型,而是要說明如何正確實現。
逐層
我們這里需要牢記整個框架:
1.?????將數據輸入神經網絡
2.?????在得出輸出之前,數據從一層流向下一層
3.?????一旦得到輸出,就可以計算出一個標量誤差。
4.?????最后,可以通過相對于參數本身減去誤差的導數來調整給定參數(權重或偏差)。
5.?????遍歷整個過程。
最重要的一步是第四步。?我們希望能夠擁有任意數量的層,以及任何類型的層。?但是如果修改/添加/刪除網絡中的一個層,網絡的輸出將會改變,誤差也將改變,誤差相對于參數的導數也將改變。無論網絡架構如何、激活函數如何、損失如何,都必須要能夠計算導數。
為了實現這一點,我們必須分別實現每一層。
每個層應該實現什么
?
我們可能構建的每一層(完全連接,卷積,最大化,丟失等)至少有兩個共同點:輸入和輸出數據。
現在重要的一部分
?
假設給出一個層相對于其輸出(?E/?Y)誤差的導數,那么它必須能夠提供相對于其輸入(?E/?X)誤差的導數。
è?°????E?ˉ??é??????a?°?-????X?Y?ˉ??éμ???
??
我們可以使用鏈規則輕松計算?E/?X的元素:
為什么是?E/?X?
對于每一層,我們需要相對于其輸入的誤差導數,因為它將是相對于前一層輸出的誤差導數。這非常重要,這是理解反向傳播的關鍵!在這之后,我們將能夠立即從頭開始編寫深度卷積神經網絡!
花樣圖解
?
基本上,對于前向傳播,我們將輸入數據提供給第一層,然后每層的輸出成為下一層的輸入,直到到達網絡的末端。
對于反向傳播,我們只是簡單使用鏈規則來獲得需要的導數。這就是為什么每一層必須提供其輸出相對于其輸入的導數。
這可能看起來很抽象,但是當我們將其應用于特定類型的層時,它將變得非常清楚。現在是編寫第一個python類的好時機。
抽象基類:Layer
所有其它層將繼承的抽象類Layer會處理簡單屬性,這些屬性是輸入,輸出以及前向和反向方法。
from abc import abstractmethod # Base class class Layer:def __init__(self):self.input = None;self.output = None;self.input_shape = None;self.output_shape = None;# computes the output Y of a layer for a given input X@abstractmethoddef forward_propagation(self, input):raise NotImplementedError# computes dE/dX for a given dE/dY (and update parameters if any)@abstractmethoddef backward_propagation(self, output_error, learning_rate):raise NotImplementedError正如你所看到的,在back_propagation函數中,有一個我沒有提到的參數,它是learning_rate。?此參數應該類似于更新策略或者在Keras中調用它的優化器,為了簡單起見,我們只是通過學習率并使用梯度下降更新我們的參數。
全連接層
現在先定義并實現第一種類型的網絡層:全連接層或FC層。FC層是最基本的網絡層,因為每個輸入神經元都連接到每個輸出神經元。
前向傳播
每個輸出神經元的值由下式計算:
使用矩陣,可以使用點積來計算每一個輸出神經元的值:
當完成前向傳播之后,現在開始做反向傳播。
反向傳播
正如我們所說,假設我們有一個矩陣,其中包含與該層輸出相關的誤差導數(?E/?Y)。?我們需要?:
1.關于參數的誤差導數(?E/?W,?E/?B)
2.關于輸入的誤差導數(?E/?X)
首先計算?E/?W,該矩陣應與W本身的大小相同:對于ixj,其中i是輸入神經元的數量,j是輸出神經元的數量。每個權重都需要一個梯度:
使用前面提到的鏈規則,可以寫出:
那么:
這就是更新權重的第一個公式!現在開始計算?E/?B:
同樣,?E/?B需要與B本身具有相同的大小,每個偏差一個梯度。?我們可以再次使用鏈規則:
得出結論:
現在已經得到?E/?W和?E/?B,我們留下?E/?X這是非常重要的,因為它將“作用”為之前層的?E/?Y。
再次使用鏈規則:
最后,我們可以寫出整個矩陣:
?????·2?????°FC?±?é?????a???????
? ?
編碼全連接層
現在我們可以用Python編寫實現:
from layer import Layer import numpy as np# inherit from base class Layer class FCLayer(Layer):# input_shape = (1,i) i the number of input neurons# output_shape = (1,j) j the number of output neuronsdef __init__(self, input_shape, output_shape):self.input_shape = input_shape;self.output_shape = output_shape;self.weights = np.random.rand(input_shape[1], output_shape[1]) - 0.5;self.bias = np.random.rand(1, output_shape[1]) - 0.5;# returns output for a given inputdef forward_propagation(self, input):self.input = input;self.output = np.dot(self.input, self.weights) + self.bias;return self.output;# computes dE/dW, dE/dB for a given output_error=dE/dY. Returns input_error=dE/dX.def backward_propagation(self, output_error, learning_rate):input_error = np.dot(output_error, self.weights.T);dWeights = np.dot(self.input.T, output_error);# dBias = output_error# update parametersself.weights -= learning_rate * dWeights;self.bias -= learning_rate * output_error;return input_error;激活層
到目前為止所做的計算都完全是線性的。用這種模型學習是沒有希望的,需要通過將非線性函數應用于某些層的輸出來為模型添加非線性。
現在我們需要為這種新類型的層(激活層)重做整個過程!
不用擔心,因為此時沒有可學習的參數,過程會快點,只需要計算?E/?X。
我們將f和f'分別稱為激活函數及其導數。前向傳播
正如將看到的,它非常簡單。對于給定的輸入X,輸出是關于每個X元素的激活函數,這意味著輸入和輸出具有相同的大小。
反向傳播
給出?E/?Y,需要計算?E/?X
注意,這里我們使用兩個矩陣之間的每個元素乘法(而在上面的公式中,它是一個點積)
編碼實現激活層
激活層的代碼非常簡單:
from layer import Layer # inherit from base class Layer class ActivationLayer(Layer):# input_shape = (1,i) i the number of input neuronsdef __init__(self, input_shape, activation, activation_prime):self.input_shape = input_shape;self.output_shape = input_shape;self.activation = activation;self.activation_prime = activation_prime;# returns the activated inputdef forward_propagation(self, input):self.input = input;self.output = self.activation(self.input);return self.output;# Returns input_error=dE/dX for a given output_error=dE/dY.# learning_rate is not used because there is no "learnable" parameters.def backward_propagation(self, output_error, learning_rate):return self.activation_prime(self.input) * output_error;可以在單獨的文件中編寫一些激活函數以及它們的導數,稍后將使用它們構建ActivationLayer:
import numpy as np # activation function and its derivative def tanh(x):return np.tanh(x);def tanh_prime(x):return 1-np.tanh(x)**2;損失函數
到目前為止,對于給定的層,我們假設給出了?E/?Y(由下一層給出)。但是最后一層怎么得到?E/?Y?我們通過簡單地手動給出最后一層的?E/?Y,它取決于我們如何定義誤差。
網絡的誤差由自己定義,該誤差衡量網絡對給定輸入數據的好壞程度。有許多方法可以定義誤差,其中一種最常見的叫做MSE - Mean Squared Error:
其中y *和y分別表示期望的輸出和實際輸出。你可以將損失視為最后一層,它將所有輸出神經元吸收并將它們壓成一個神經元。與其他每一層一樣,需要定義?E/?Y。除了現在,我們終于得到E!
以下是兩個python函數,可以將它們放在一個單獨的文件中,將在構建網絡時使用。
import numpy as np# loss function and its derivative def mse(y_true, y_pred):return np.mean(np.power(y_true-y_pred, 2));def mse_prime(y_true, y_pred):return 2*(y_pred-y_true)/y_true.size;網絡類
到現在幾乎完成了!我們將構建一個Network類來創建神經網絡,非常容易,類似于第一張圖片!
我注釋了代碼的每一部分,如果你掌握了前面的步驟,那么理解它應該不會太復雜。
from layer import Layerclass Network:def __init__(self):self.layers = [];self.loss = None;self.loss_prime = None;# add layer to networkdef add(self, layer):self.layers.append(layer);# set loss to usedef use(self, loss, loss_prime):self.loss = loss;self.loss_prime = loss_prime;# predict output for given inputdef predict(self, input):# sample dimension firstsamples = len(input);result = [];# run network over all samplesfor i in range(samples):# forward propagationoutput = input[i];for layer in self.layers:# output of layer l is input of layer l+1output = layer.forward_propagation(output);result.append(output);return result;# train the networkdef fit(self, x_train, y_train, epochs, learning_rate):# sample dimension firstsamples = len(x_train);# training loopfor i in range(epochs):err = 0;for j in range(samples):# forward propagationoutput = x_train[j];for layer in self.layers:output = layer.forward_propagation(output);# compute loss (for display purpose only)err += self.loss(y_train[j], output);# backward propagationerror = self.loss_prime(y_train[j], output);# loop from end of network to beginningfor layer in reversed(self.layers):# backpropagate dEerror = layer.backward_propagation(error, learning_rate);# calculate average error on all sampleserr /= samples;print('epoch %d/%d error=%f' % (i+1,epochs,err));構建一個神經網絡
最后!我們可以使用我們的類來創建一個包含任意數量層的神經網絡!為了簡單起見,我將向你展示如何構建......一個XOR。
from network import Network from fc_layer import FCLayer from activation_layer import ActivationLayer from losses import * from activations import * import numpy as np# training data x_train = np.array([[[0,0]], [[0,1]], [[1,0]], [[1,1]]]); y_train = np.array([[[0]], [[1]], [[1]], [[0]]]);# network net = Network(); net.add(FCLayer((1,2), (1,3))); net.add(ActivationLayer((1,3), tanh, tanh_prime)); net.add(FCLayer((1,3), (1,1))); net.add(ActivationLayer((1,1), tanh, tanh_prime));# train net.use(mse, mse_prime); net.fit(x_train, y_train, epochs=1000, learning_rate=0.1);# test out = net.predict(x_train); print(out);同樣,我認為不需要強調很多事情,只需要仔細訓練數據,應該能夠先獲得樣本維度。例如,對于xor問題,樣式應為(4,1,2)。
結果
$ python xor.py epoch 1/1000 error=0.322980 epoch 2/1000 error=0.311174 epoch 3/1000 error=0.307195 ... epoch 998/1000 error=0.000243 epoch 999/1000 error=0.000242 epoch 1000/1000 error=0.000242 [array([[ 0.00077435]]), array([[ 0.97760742]]), array([[ 0.97847793]]), array([[-0.00131305]])]卷積層
這篇文章開始很長,所以我不會描述實現卷積層的所有步驟。但是,這是我做的一個實現:
from layer import Layer from scipy import signal import numpy as np# inherit from base class Layer # This convolutional layer is always with stride 1 class ConvLayer(Layer):# input_shape = (i,j,d)# kernel_shape = (m,n)# layer_depth = output depthdef __init__(self, input_shape, kernel_shape, layer_depth):self.input_shape = input_shape;self.input_depth = input_shape[2];self.kernel_shape = kernel_shape;self.layer_depth = layer_depth;self.output_shape = (input_shape[0]-kernel_shape[0]+1, input_shape[1]-kernel_shape[1]+1, layer_depth);self.weights = np.random.rand(kernel_shape[0], kernel_shape[1], self.input_depth, layer_depth) - 0.5;self.bias = np.random.rand(layer_depth) - 0.5;# returns output for a given inputdef forward_propagation(self, input):self.input = input;self.output = np.zeros(self.output_shape);for k in range(self.layer_depth):for d in range(self.input_depth):self.output[:,:,k] += signal.correlate2d(self.input[:,:,d], self.weights[:,:,d,k], 'valid') + self.bias[k];return self.output;# computes dE/dW, dE/dB for a given output_error=dE/dY. Returns input_error=dE/dX.def backward_propagation(self, output_error, learning_rate):in_error = np.zeros(self.input_shape);dWeights = np.zeros((self.kernel_shape[0], self.kernel_shape[1], self.input_depth, self.layer_depth));dBias = np.zeros(self.layer_depth);for k in range(self.layer_depth):for d in range(self.input_depth):in_error[:,:,d] += signal.convolve2d(output_error[:,:,k], self.weights[:,:,d,k], 'full');dWeights[:,:,d,k] = signal.correlate2d(self.input[:,:,d], output_error[:,:,k], 'valid');dBias[k] = self.layer_depth * np.sum(output_error[:,:,k]);self.weights -= learning_rate*dWeights;self.bias -= learning_rate*dBias;return in_error;它背后的數學實際上并不復雜!這是一篇很好的文章,你可以找到?E/?W,?E/?B和?E/?X的解釋和計算。
如果你想驗證你的理解是否正確,請嘗試自己實現一些網絡層,如MaxPooling,Flatten或Dropout
GitHub庫
你可以在GitHub庫中找到用于該文章的完整代碼。
?
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的(Python)零起步数学+神经网络入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 像数据科学家一样思考:12步指南(中)
- 下一篇: Flutter路由管理代码这么长长长长长