程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

Python 實現一個簡單的神經網絡(附代碼)

編輯: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 np
def sigmoid(x):
# 我們的激活函數: f(x) = 1 / (1 + e^(-x))
return 1 / (1 + np.exp(-x))
class Neuron:
def __init__(self, weights, bias):
self.weights = weights
self.bias = bias
def feedforward(self, inputs):
# 加權輸入,加入偏置,然後使用激活函數
total = np.dot(self.weights, inputs) + self.bias
return 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 here
class 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_o1
network = OurNeuralNetwork()
x = np.array([2, 3])
print(network.feedforward(x)) # 0.7216325609518421

結果正確,看上去沒問題。

訓練神經網絡 第一部分

現在有這樣的數據:

姓名體重(磅)身高 (英寸)性別Alice13365FBob16072MCharlie15270MDiana12060F

接下來我們用這個數據來訓練神經網絡的權重和截距項,從而可以根據身高體重預測性別:

我們用0和1分別表示男性(M)和女性(F),並對數值做了轉化:

姓名體重 (減 135)身高 (減 66)性別Alice-2-11Bob2560Charlie1740Diana-15-61

我這裡是隨意選取了135和66來標准化數據,通常會使用平均值。

損失

在訓練網絡之前,我們需要量化當前的網絡是『好』還是『壞』,從而可以尋找更好的網絡。這就是定義損失的目的。

我們在這裡用平均方差(MSE)損失: ,讓我們仔細看看:

  • n是樣品數,這裡等於4(Alice、Bob、Charlie和Diana)。

  • y表示要預測的變量,這裡是性別。

  • 是變量的真實值(『正確答案』)。例如,Alice的就是1(男性)。

  • 變量的預測值。這就是我們網絡的輸出。

被稱為方差(squared error)。我們的損失函數就是所有方差的平均值。預測效果於浩,損失就越少。

更好的預測 = 更少的損失!

訓練網絡 = 最小化它的損失。

損失計算例子

假設我們的網絡總是輸出0,換言之就是認為所有人都是男性。損失如何?

Namey_truey_pred(y_true - y_pred)^2Alice101Bob000Charlie000Diana101

代碼:MSE損失

下面是計算MSE損失的代碼:

import numpy as np
def 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的方差:

也可以把損失看成是權重和截距項的函數。讓我們給網絡標上權重和截距項:

這樣我們就可以把網絡的損失表示為:

假設我們要優化 ,當我們改變 時,損失會怎麼變化?可以用來回答這個問題,怎麼計算?

首先,讓我們用來改寫這個偏導數:

因為我們已經知道 ,所以我們可以計算

現在讓我們來搞定 分別是其所表示的神經元的輸出,我們有:

由於 只會影響 (不會影響 ),所以:

,我們也可以這麼做:

在這裡, 是身高, 是體重。這是我們第二次看到 (S型函數的導數)了。求解:

我們已經把 分解成了幾個我們能計算的部分:

這種計算偏導的方法叫『反向傳播算法』(backpropagation)。

好多數學符號,如果你還沒搞明白的話,我們來看一個實際例子。

例子:計算偏導數

我們還是看數據集中只有Alice的情況:

把所有的權重和截距項都分別初始化為1和0。在網絡中做前饋計算:

網絡的輸出是 ,對於Male(0)或者Female(1)都沒有太強的傾向性。算一下

提示:前面已經得到了S型激活函數的導數

搞定!這個結果的意思就是增加也會隨之輕微上升。

現在訓練神經網絡已經萬事俱備了!我們會使用名為隨機梯度下降法的優化算法來優化網絡的權重和截距項,實現損失的最小化。核心就是這個更新公式:

是一個常數,被稱為學習率,用於調整訓練的速度。我們要做的就是用 減去

我們的訓練過程是這樣的:

  1. 從我們的數據集中選擇一個樣本,用隨機梯度下降法進行優化——每次我們都只針對一個樣本進行優化;

  2. 計算每個權重或截距項對損失的偏導(例如等);

  3. 用更新等式更新每個權重和截距項;

  4. 重復第一步;

代碼:一個完整的神經網絡

我們終於可以實現一個完整的神經網絡了:

姓名身高 (減 135)體重 (減 66)GenderAlice-2-11Bob2560Charlie1740Diana-15-61

總體代碼

import numpy as np
def 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):
# 權重,Weights
self.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()
# 截距項,Biases
self.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 o1
def 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.1
epochs = 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.b1
h1 = sigmoid(sum_h1)
sum_h2 = self.w3 * x[0] + self.w4 * x[1] + self.b2
h2 = sigmoid(sum_h2)
sum_o1 = self.w5 * h1 + self.w6 * h2 + self.b3
o1 = 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 o1
d_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 h1
d_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 h2
d_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 h1
self.w1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w1
self.w2 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w2
self.b1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_b1
# Neuron h2
self.w3 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w3
self.w4 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w4
self.b2 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_b2
# Neuron o1
self.w5 -= learn_rate * d_L_d_ypred * d_ypred_d_w5
self.w6 -= learn_rate * d_L_d_ypred * d_ypred_d_w6
self.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, # Alice
0, # Bob
0, # Charlie
1, # 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;

  • 在浏覽器中實現神經網絡;

  • 其他類型的激活函數;

  • 其他類型的優化器;

  • 學習卷積神經網絡,這給計算機視覺領域帶來了革命;

  • 學習遞歸神經網絡,常用語自然語言處理;

附加一段代碼

#!/usr/bin/pyton3
import numpy as np
import matplotlib.pyplot as plt
# Activation Function
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# Neural Unit
class Perceptron(object):
def __init__(self, input_size: int, initializer: list = None, activation_func: str = None):
# W[0] is the bias
self.input_size = input_size
# W is parameters of Perceptron
self.n_W = self.input_size + 1
self.W = np.random.uniform(low=0.0, high=1.0, size=self.n_W)
# X is the input vector of Perceptron
self.X = None
self.output = 0.0
self.delta_W = np.array([0] * self.n_W, dtype=np.float)
self.delta_X = np.array([0] * input_size, dtype=np.float)
self.activation_func = activation_func
if initializer:
assert len(initializer) == self.n_W
self.W = initializer
def forward(self, X):
assert len(X) == self.input_size
self.X = np.array(X, dtype=float)
y = np.sum(self.W[1:] * self.X) + self.W[0]
if self.activation_func == 'sigmoid':
self.output = sigmoid(y)
else:
self.output = y
return self.output
def update(self, lr):
self.W = self.W + lr * self.delta_W
self.delta_W = np.array([0] * self.n_W, dtype=np.float)
def __call__(self, X):
return self.forward(X)
# Neural Layer
class Layer(object):
def __init__(self, input_size: int, output_size: int, activation_func='sigmoid', lr=0.1):
self.input_size = input_size
self.output_size = output_size
self.net = np.array(
[Perceptron(input_size=input_size, activation_func=activation_func) for _ in range(output_size)])
self.activation_func = activation_func
self.inputs = np.array([0] * input_size, dtype=np.float)
self.lr = lr
self.outputs = np.array([0] * output_size, dtype=np.float)
def forward(self, X):
self.inputs = np.array(X, dtype=np.float)
self.outputs = np.array([p(X) for p in self.net])
return self.outputs
def __call__(self, X):
return self.forward(X)
def backward(self, delta_outputs):
assert len(delta_outputs) == len(self.net)
for idx in range(self.output_size):
delta_output = delta_outputs[idx]
p = self.net[idx]
o = self.outputs[idx]
if self.activation_func == 'sigmoid':
# W0 is the bias
p.delta_W = delta_output * o * (1 - o) * np.array([1] + list(p.X)) # expand X for W_0
p.delta_X = delta_output * o * (1 - o) * p.W[1:]
else:
# linear
p.delta_W = delta_output * np.array([1] + list(p.X))
# W0 is the bias
p.delta_X = delta_output * p.W[1:]
def update(self):
for p in self.net:
p.update(self.lr)
if __name__ == '__main__':
# standard version ============================
samples = [[[-2, -1], 1],
[[25, 6], 0],
[[17, 4], 0],
[[-15, -6], 1]]
# training
layer1 = Layer(2, 10)
layer2 = Layer(10, 1, activation_func='')
for i in range(1000):
# iteration
print(f'iteration {
i}')
text_X = samples[3][0]
text_y_d = samples[3][1]
test_y = layer2(layer1((samples[3][0])))[0]
print(f'X:{
text_X}, y_d:{
text_y_d}, y:{
test_y}')
for X, y_d in samples:
# forward
y = layer2(layer1(X))[0]
# backward layer2 -> layer1
err = y_d - y # delta_outputs of layer 2
layer2.backward([err])
delta_outputs = np.array([0.0] * layer2.input_size, dtype=np.float) # delta_outputs of layer 1
for p in layer2.net:
delta_outputs += p.delta_X
layer1.backward(delta_outputs)
# update gradient
layer2.update()
layer1.update()
# result
for X, y_d in samples:
y = layer2(layer1(X))[0]
print(f'The final result: X:{
X}, y_d:{
y_d}, y:{
y}')
# # simple version ============================
# samples = [[[0, 1], 1],
# [[1, 0], -1]]
# # training
# layer1 = Layer(2, 1, activation_func='')
# for i in range(100):
# # iteration
# print(f'iteration {i}')
# for X, y_d in samples:
# # forward
# y = layer1(X)[0]
# # backward
# err = y_d - y
# layer1.backward([err])
# print(f'W:{layer1.net[0].W}')
# print(f'delta_W:{layer1.net[0].delta_W}')
# layer1.update()
# print(f'X:{X}, y_d:{y_d}, y:{y}')

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved