Python 实现一个简单的神经网络(附代码)
目錄
?前言?
?磚塊:神經元?
📌一個簡單的例子📌
📌編碼一個神經元?📌
📌把神經元組裝成網絡?📌
📌例子:前饋📌
📌編碼神經網絡:前饋📌
📌訓練神經網絡 第一部分📌
📌損失📌
📌損失計算例子📌
📌代碼:MSE損失📌
📌訓練神經網絡 第二部分📌
📌例子:計算偏導數📌
📌代碼:一個完整的神經網絡📌?
📌后話📌?
?前言?
以下內容我們用Python從頭實現一個神經網絡來理解神經網絡的原理。
首先讓我們看看神經網絡的基本單位,神經元。神經元接受輸入,對其做一些數據操作,然后產生輸出。例如,這是一個2-輸入神經元:
這里發生了三個事情。首先,每個輸入都跟一個權重相乘(紅色):
然后,加權后的輸入求和,加上一個偏差b(綠色):
最后,這個結果傳遞給一個激活函數f:
激活函數的用途是將一個無邊界的輸入,轉變成一個可預測的形式。常用的激活函數就就是S型函數:?
S型函數的值域是(0, 1)。簡單來說,就是把(?∞, +∞)壓縮到(0, 1) ,很大的負數約等于0,很大的正數約等于1。?
📌一個簡單的例子📌
假設我們有一個神經元,激活函數就是S型函數,其參數如下:?
w=[0,1],b=4
w=[0,1]就是以向量的形式表示w1=0,w2=1。現在,我們給這個神經元一個輸入[2,3]。我們用點積來表示:
當輸入是[2, 3]時,這個神經元的輸出是0.999。給定輸入,得到輸出的過程被稱為前饋(feedforward)。?
📌編碼一個神經元?📌
讓我們來實現一個神經元!用Python的NumPy庫來完成其中的數學計算:
import numpy as npdef sigmoid(x):# 我們的激活函數: f(x) = 1 / (1 + e^(-x))return 1 / (1 + np.exp(-x))class Neuron:def __init__(self, weights, bias):self.weights = weightsself.bias = biasdef feedforward(self, inputs):# 加權輸入,加入偏置,然后使用激活函數total = np.dot(self.weights, inputs) + self.biasreturn sigmoid(total)weights = np.array([0, 1]) # w1 = 0, w2 = 1 bias = 4 # b = 4 n = Neuron(weights, bias)x = np.array([2, 3]) # x1 = 2, x2 = 3 print(n.feedforward(x)) # 0.9990889488055994📌把神經元組裝成網絡?📌
所謂的神經網絡就是一堆神經元。這就是一個簡單的神經網絡:
這個網絡有兩個輸入,一個有兩個神經元(?和?)的隱藏層,以及一個有一個神經元(?) )的輸出層。要注意,的輸入就是和的輸出,這樣就組成了一個網絡。?
隱藏層就是輸入層和輸出層之間的層,隱藏層可以是多層的。?
📌例子:前饋📌
我們繼續用前面圖中的網絡,假設每個神經元的權重都是??,截距項也相同??,激活函數也都是S型函數。分別用?表示相應的神經元的輸出。
當輸入 x=[2,3]時,會得到什么結果??
這個神經網絡對輸入[2,3]的輸出是0.7216,很簡單。?
📌編碼神經網絡:前饋📌
import numpy as np# ... code from previous section hereclass OurNeuralNetwork:'''A neural network with:- 2 inputs- a hidden layer with 2 neurons (h1, h2)- an output layer with 1 neuron (o1)Each neuron has the same weights and bias:- w = [0, 1]- b = 0'''def __init__(self):weights = np.array([0, 1])bias = 0# 這里是來自前一節的神經元類self.h1 = Neuron(weights, bias)self.h2 = Neuron(weights, bias)self.o1 = Neuron(weights, bias)def feedforward(self, x):out_h1 = self.h1.feedforward(x)out_h2 = self.h2.feedforward(x)# o1的輸入是h1和h2的輸出out_o1 = self.o1.feedforward(np.array([out_h1, out_h2]))return out_o1network = OurNeuralNetwork() x = np.array([2, 3]) print(network.feedforward(x)) # 0.7216325609518421結果正確,看上去沒問題。?
📌訓練神經網絡 第一部分📌
現在有這樣的數據:?
| Alice | 133 | 65 | F |
| Bob | 160 | 72 | M |
| Charlie | 152 | 70 | M |
| Diana | 120 | 60 | F |
接下來我們用這個數據來訓練神經網絡的權重和截距項,從而可以根據身高體重預測性別:?
我們用0和1分別表示男性(M)和女性(F),并對數值做了轉化:?
| Alice | -2 | -1 | 1 |
| Bob | 25 | 6 | 0 |
| Charlie | 17 | 4 | 0 |
| Diana | -15 | -6 | 1 |
我這里是隨意選取了135和66來標準化數據,通常會使用平均值。?
📌損失📌
在訓練網絡之前,我們需要量化當前的網絡是『好』還是『壞』,從而可以尋找更好的網絡。這就是定義損失的目的。
我們在這里用平均方差(MSE)損失:?,讓我們仔細看看:
-
n是樣品數,這里等于4(Alice、Bob、Charlie和Diana)。
-
y表示要預測的變量,這里是性別。
-
?是變量的真實值(『正確答案』)。例如,Alice的就是1(男性)。?
-
?變量的預測值。這就是我們網絡的輸出。
被稱為方差(squared error)。我們的損失函數就是所有方差的平均值。預測效果于浩,損失就越少。
更好的預測 = 更少的損失!
訓練網絡 = 最小化它的損失。
📌損失計算例子📌
假設我們的網絡總是輸出0,換言之就是認為所有人都是男性。損失如何??
| Alice | 1 | 0 | 1 |
| Bob | 0 | 0 | 0 |
| Charlie | 0 | 0 | 0 |
| Diana | 1 | 0 | 1 |
?
📌代碼:MSE損失📌
?下面是計算MSE損失的代碼:
import numpy as npdef mse_loss(y_true, y_pred):# y_true and y_pred are numpy arrays of the same length.return ((y_true - y_pred) ** 2).mean()y_true = np.array([1, 0, 0, 1]) y_pred = np.array([0, 0, 0, 0])print(mse_loss(y_true, y_pred)) # 0.5如果你不理解這段代碼,可以看看NumPy的快速入門中關于數組的操作。?
📌訓練神經網絡 第二部分📌
現在我們有了一個明確的目標:最小化神經網絡的損失。通過調整網絡的權重和截距項,我們可以改變其預測結果,但如何才能逐步地減少損失??
這一段內容涉及到多元微積分,如果不熟悉微積分的話,可以跳過這些數學內容。?
這一段內容涉及到多元微積分,如果不熟悉微積分的話,可以跳過這些數學內容。?
為了簡化問題,假設數據集中只有Alice,那均方差損失就只是Alice的方差:?
| Alice | 1 | 0 | 1 |
也可以把損失看成是權重和截距項的函數。讓我們給網絡標上權重和截距項:?
這樣我們就可以把網絡的損失表示為:?
假設我們要優化?,當我們改變?時,損失會怎么變化?可以用來回答這個問題,怎么計算??
首先,讓我們用來改寫這個偏導數:?
因為我們已經知道??,所以我們可以計算?
現在讓我們來搞定??。分別是其所表示的神經元的輸出,我們有:?
由于??只會影響??(不會影響??),所以:?
對??,我們也可以這么做:?
在這里,?是身高,?是體重。這是我們第二次看到??(S型函數的導數)了。求解:
我們已經把??分解成了幾個我們能計算的部分:
這種計算偏導的方法叫『反向傳播算法』(backpropagation)。?
好多數學符號,如果你還沒搞明白的話,我們來看一個實際例子。?
📌例子:計算偏導數📌
我們還是看數據集中只有Alice的情況:
把所有的權重和截距項都分別初始化為1和0。在網絡中做前饋計算:?
網絡的輸出是??,對于Male(0)或者Female(1)都沒有太強的傾向性。算一下?
?
?
?
提示:前面已經得到了S型激活函數的導數 ?。?
搞定!這個結果的意思就是增加,也會隨之輕微上升。?
現在訓練神經網絡已經萬事俱備了!我們會使用名為隨機梯度下降法的優化算法來優化網絡的權重和截距項,實現損失的最小化。核心就是這個更新公式:?
是一個常數,被稱為學習率,用于調整訓練的速度。我們要做的就是用??減去
-
如果??是正數,??變小,?會下降。
-
如果??是負數,?變大,?會上升。??
我們的訓練過程是這樣的:?
從我們的數據集中選擇一個樣本,用隨機梯度下降法進行優化——每次我們都只針對一個樣本進行優化;
計算每個權重或截距項對損失的偏導(例如?、等);
用更新等式更新每個權重和截距項;
重復第一步;
📌代碼:一個完整的神經網絡📌?
我們終于可以實現一個完整的神經網絡了:?
| Alice | -2 | -1 | 1 |
| Bob | 25 | 6 | 0 |
| Charlie | 17 | 4 | 0 |
| Diana | -15 | -6 | 1 |
總體代碼
import numpy as npdef sigmoid(x):# Sigmoid activation function: f(x) = 1 / (1 + e^(-x))return 1 / (1 + np.exp(-x))def deriv_sigmoid(x):# Derivative of sigmoid: f'(x) = f(x) * (1 - f(x))fx = sigmoid(x)return fx * (1 - fx)def mse_loss(y_true, y_pred):# y_true和y_pred是相同長度的numpy數組。return ((y_true - y_pred) ** 2).mean()class OurNeuralNetwork:'''A neural network with:- 2 inputs- a hidden layer with 2 neurons (h1, h2)- an output layer with 1 neuron (o1)*** 免責聲明 ***:下面的代碼是為了簡單和演示,而不是最佳的。真正的神經網絡代碼與此完全不同。不要使用此代碼。相反,讀/運行它來理解這個特定的網絡是如何工作的。'''def __init__(self):# 權重,Weightsself.w1 = np.random.normal()self.w2 = np.random.normal()self.w3 = np.random.normal()self.w4 = np.random.normal()self.w5 = np.random.normal()self.w6 = np.random.normal()# 截距項,Biasesself.b1 = np.random.normal()self.b2 = np.random.normal()self.b3 = np.random.normal()def feedforward(self, x):# X是一個有2個元素的數字數組。h1 = sigmoid(self.w1 * x[0] + self.w2 * x[1] + self.b1)h2 = sigmoid(self.w3 * x[0] + self.w4 * x[1] + self.b2)o1 = sigmoid(self.w5 * h1 + self.w6 * h2 + self.b3)return o1def train(self, data, all_y_trues):'''- data is a (n x 2) numpy array, n = # of samples in the dataset.- all_y_trues is a numpy array with n elements.Elements in all_y_trues correspond to those in data.'''learn_rate = 0.1epochs = 1000 # 遍歷整個數據集的次數for epoch in range(epochs):for x, y_true in zip(data, all_y_trues):# --- 做一個前饋(稍后我們將需要這些值)sum_h1 = self.w1 * x[0] + self.w2 * x[1] + self.b1h1 = sigmoid(sum_h1)sum_h2 = self.w3 * x[0] + self.w4 * x[1] + self.b2h2 = sigmoid(sum_h2)sum_o1 = self.w5 * h1 + self.w6 * h2 + self.b3o1 = sigmoid(sum_o1)y_pred = o1# --- 計算偏導數。# --- Naming: d_L_d_w1 represents "partial L / partial w1"d_L_d_ypred = -2 * (y_true - y_pred)# Neuron o1d_ypred_d_w5 = h1 * deriv_sigmoid(sum_o1)d_ypred_d_w6 = h2 * deriv_sigmoid(sum_o1)d_ypred_d_b3 = deriv_sigmoid(sum_o1)d_ypred_d_h1 = self.w5 * deriv_sigmoid(sum_o1)d_ypred_d_h2 = self.w6 * deriv_sigmoid(sum_o1)# Neuron h1d_h1_d_w1 = x[0] * deriv_sigmoid(sum_h1)d_h1_d_w2 = x[1] * deriv_sigmoid(sum_h1)d_h1_d_b1 = deriv_sigmoid(sum_h1)# Neuron h2d_h2_d_w3 = x[0] * deriv_sigmoid(sum_h2)d_h2_d_w4 = x[1] * deriv_sigmoid(sum_h2)d_h2_d_b2 = deriv_sigmoid(sum_h2)# --- 更新權重和偏差# Neuron h1self.w1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w1self.w2 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w2self.b1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_b1# Neuron h2self.w3 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w3self.w4 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w4self.b2 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_b2# Neuron o1self.w5 -= learn_rate * d_L_d_ypred * d_ypred_d_w5self.w6 -= learn_rate * d_L_d_ypred * d_ypred_d_w6self.b3 -= learn_rate * d_L_d_ypred * d_ypred_d_b3# --- 在每次epoch結束時計算總損失 if epoch % 10 == 0:y_preds = np.apply_along_axis(self.feedforward, 1, data)loss = mse_loss(all_y_trues, y_preds)print("Epoch %d loss: %.3f" % (epoch, loss))# 定義數據集 data = np.array([[-2, -1], # Alice[25, 6], # Bob[17, 4], # Charlie[-15, -6], # Diana ]) all_y_trues = np.array([1, # Alice0, # Bob0, # Charlie1, # Diana ])# 訓練我們的神經網絡! network = OurNeuralNetwork() network.train(data, all_y_trues)隨著網絡的學習,損失在穩步下降。?
現在我們可以用這個網絡來預測性別了:?
# 做一些預測 emily = np.array([-7, -3]) # 128 磅, 63 英寸 frank = np.array([20, 2]) # 155 磅, 68 英寸 print("Emily: %.3f" % network.feedforward(emily)) # 0.951 - F print("Frank: %.3f" % network.feedforward(frank)) # 0.039 - M📌后話📌?
搞定了一個簡單的神經網絡,快速回顧一下:
-
介紹了神經網絡的基本結構——神經元;
-
在神經元中使用S型激活函數;
-
神經網絡就是連接在一起的神經元;
-
構建了一個數據集,輸入(或特征)是體重和身高,輸出(或標簽)是性別;
-
學習了損失函數和均方差損失;
-
訓練網絡就是最小化其損失;
-
用反向傳播方法計算偏導;
-
用隨機梯度下降法訓練網絡;
接下來你還可以:
-
用機器學習庫實現更大更好的神經網絡,例如TensorFlow、Keras和PyTorch;
-
在瀏覽器中實現神經網絡;
-
其他類型的激活函數;
-
其他類型的優化器;
-
學習卷積神經網絡,這給計算機視覺領域帶來了革命;
-
學習遞歸神經網絡,常用語自然語言處理;
?參考文章:用 Python 從頭實現一個神經網絡(附代碼) (qq.com)
總結
以上是生活随笔為你收集整理的Python 实现一个简单的神经网络(附代码)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一位算法工程师对自己工作的反思,写的不错
- 下一篇: python locust 时间戳过期_