線性回歸網絡
在神經網絡基礎0:線性邏輯回歸理論實現章節,我們通過對y = wx+b的預測,實現了一個最簡單的線性回歸模型;線性回歸模型也是最簡單的神經網絡模型,只有一個輸入參數,一個神經元節點和一個輸出參數
神經元(Neurons),它是神經網絡的基本單元。神經元先獲得輸入,然后執行某些數學運算后,再產生一個輸出。
在這個神經元中,輸入總共經歷了2步數學運算
先將一個輸入乘以權重(weight):
x→x × w,
再加上一個偏置(bias):
x × w+b
得到最后的結果:y = x × w+b
這算是最簡單的神經網絡了,只有一個神經元,并且沒有激活函數對其進行處理(因為輸出y是連續的情況下,輸出層的激活函數可以使用線性函數y = x)
接下來,我們將根據一組真實的的場景數據,搭建一個具備兩個神經元的網絡
神經網絡實現方法
我們有以上身高,體重,和性別數據,接下來將搭建一個神經網絡,能夠根據輸入的體重和身高,預測該群眾屬于什么性別
基本模塊搭建-神經元
神經元(Neurons),它是神經網絡的基本單元。神經元先獲得輸入,然后執行某些數學運算后,再產生一個輸出。以下是一個2輸入神經元的例子:
在這個神經元中,輸入總共經歷了3步數學運算,
先將兩個輸入乘以權重(weight):
x1→x1 × w1
x2→x2 × w2
把兩個結果想加,再加上一個偏置(bias):
(x1 × w1)+(x2 × w2)+ b
上式的線性輸出區間為整個實數范圍,而我們預測要求輸出范圍在[0,1]之間,所以還需要使用激活函數對上式的線性函數輸出進行處理(之所以使用激活函數,是因為身高和體重的的數據范圍是不一樣的,如果直接使用原始的數據輸出,那么他們對性別的影響程度的影響程度將是不一樣的;上文中,我們直接將身高和體重直接減去一個固定值,也是相當于使用了一個激活函數,只不過這個激活函數是線性的(y = x - b),實際使用中,需要使用非線性函數對每個神經元的輸出進行修正, 關于激活函數我們為在單獨的章節中進行討論)
y = f(x1 × w1 + x2 × w2 + b)
一種常用的激活函數是sigmoid函數:
sigmoid函數的輸出介于0和1,我們可以理解為它把 (?∞,+∞) 范圍內的數壓縮到 (0, 1)以內。正值越大輸出越接近1,負向數值越大輸出越接近0
為了計算方便,我們將輸入值統一減去一個常量:(因為當輸出值很大的時候,激活函數的斜率(梯度)很小。因此,在這個區域內,梯度下降算法會運行得比較慢。在實際應用中,應盡量避免是的輸出落在這個區域,使輸出盡可能限定在零值附近,從而提高梯度下降算法運算速度)
舉個例子,上面神經元里的權重和偏置取如下數值:
w=[0,1]
b = 4
w=[0,1]是w1=0、w2=1的向量形式寫法。給神經元一個輸入(張二的身高體重數據)x=[27,17],可以用向量點積的形式把神經元的輸出計算出來:
w·x+b =(x1 × w1)+(x2 × w2)+ b = 0×27+1×17+4=21
y=f(w?X+b)=f(21)=0.9999999992417439
以上步驟的Python代碼是:
imort numpy as np
def sigmoid(x):
# Our activation function: 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):
# Weight inputs, add bias, then use the activation function
total = np.dot(self.weights, inputs) + self.bias
print("total:",total)
return sigmoid(total)
weights = np.array([0, 1]) # w1 = 0, w2 = 1
bias = 4 # b = 4
n = Neuron(weights, bias)
x = np.array([133, 165]) #
print(n.feedforward(x)) # 1
搭建神經網絡
神經網絡就是把一堆神經元連接在一起,下面是一個根據我們的數據搭建的簡單神經網絡的:
這個網絡有2個輸入(分別代表身高和年齡)、一個包含2個神經元的隱藏層(h1和h2)、包含1個神經元的輸出層o1。
隱藏層是夾在輸入輸入層和輸出層之間的部分,一個神經網絡可以有多個隱藏層;把神經元的輸入向前傳遞獲得輸出的過程稱為前饋(feedforward)
我們假設上面的網絡里所有神經元都具有相同的權重w=[0,1]和偏置b=0,激活函數都是sigmoid,那么我們會得到什么輸出呢?
h1=h2=f(w?x+b)=f((0×133)+(1×165)+0)
=f(165)
=1.0
o1=f(w?[h1,h2]+b)=f((0?h1)+(1?h2)+0)
=f(1.0)
=0.7310
# -*- coding: utf-8 -*-
"""
Created on Sun Apr 5 22:35:35 2020
@author: Administrator
"""
import numpy as np
def sigmoid(x):
# Our activation function: f(x) = 1 / (1 + e^(-x))
return 1 / (1 + np.exp(-x))
#--------create Neuron-----------
class Neuron:
def __init__(self, weights, bias):
self.weights = weights
self.bias = bias
def feedforward(self, inputs):
# Weight inputs, add bias, then use the activation function
total = np.dot(self.weights, inputs) + self.bias
print("total:",total)
return sigmoid(total)
weights = np.array([0, 1]) # w1 = 0, w2 = 1
bias = 4 # b = 4
n = Neuron(weights, bias)
x = np.array([133, 165]) #
print("Neuron:", n.feedforward(x)) #
#--------create NeuronNetwork-----------
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
# The Neuron class here is from the previous section
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)
# The inputs for o1 are the outputs from h1 and h2
out_o1 = self.o1.feedforward(np.array([out_h1, out_h2]))
return out_o1
network = OurNeuralNetwork()
x = np.array([133, 165])
print(network.feedforward(x)) # 0.7310585786300049
訓練神經網絡
現在我們已經學會了如何搭建神經網絡,現在我們來學習如何訓練它。
- 準備數據
我們要訓練的數據如下(性別男用1表示,女用0表示):
- 確定要使用的損失函數
在訓練神經網絡之前,我們需要有一個標準定義它到底好不好,以便我們進行改進,這就是損失(loss)。
- 求各個權重和偏置的偏導數
在神經網絡基礎: 線性回歸理論實現章節我們已經討論過損失函數的意義,如果要使得損失函數求得最小值,我們需要運用梯度下降法不斷更新各個變量的值,直到達到穩定:
而在我們現在的場景中,損失函數是包含以下九個變量的函數,不過都符合以上變量更新公式
因此為了迭代更新L,使得其值不斷減小,我們需要求出各個變量的偏導數。
在求各個變量的偏導數之前,我們先求出,在輸入值x = [x0,x1] = [weight,height]已知的情況下,各個神經元節點的輸出表達公式; 因為這些表達式,在計算偏導數的時候會用到
各個神經元的輸出表達式:
上文已經說過,每個神經元的輸出,都需要經過激活函數的轉換處理,將其中從[-∞,∞]轉換到[0,1],已符合我們的值的輸出預期
在f(x)的基礎上,我們可以求出
以上計算過程中,各個神經元的輸入等于各個前一層神經元的加權和,這個過程被稱為神經網絡的前饋過程
各個神經元的偏導數
已知損失函數的表達式如下:
其中,N表示樣本數據的大小,當我們對單個樣本數據計算損失函數時,其表達式為:
我們先求得求得L對ypred的導數:
根據鏈式求導法則:
因此,如果要求得各個變量參數的偏導數,我們從后向前求總各個參數的偏導數:
這種從后之前求各個神經元上輸入變量的的偏導數的過程,稱為神經網絡的后饋過程
- 梯度下降更新每個權重和偏置
通過以上公式,求出所有權重和偏置后,就可以通過梯度下降法,更新每個權重和偏置
總結下訓練過程:
1、從數據集中選擇一個樣本;
2、確定和定義要使用的損失函數
3、計算每個神經元的輸出表達式,并計算損失函數對所有權重和偏置的偏導數;
4、使用更新公式更新每個權重和偏置;
5、回到第1步。
實現代碼為:
import numpy as np
#激活函數
def sigmoid(x):
# Our activation function: f(x) = 1 / (1 + e^(-x))
return 1 / (1 + np.exp(-x))
#激活函數求導
def deriv_sigmoid(x):
fx = sigmoid(x)
return fx * (1 - fx)
#損失函數
def mse_loss(y_pred, y_true):
return ((y_pred - y_true) ** 2).mean()
#--------create NeuronNetwork-----------
# Define dataset
data = np.array([
[0, 0],
[27, 17],
[19, 12],
[-13, -13],
[27, 10],
[-13, 0],
])
all_y_trues = np.array([
0,
1,
1,
0,
1,
0,
])
class OurNeuralNetwork:
'''
A neural network with:
- 2 inputs
- a hidden layer with 2 neurons (h1, h2)
- an output layer with 1 neuron (o1)
*** DISCLAIMER ***:
The code below is intended to be simple and educational, NOT optimal.
Real neural net code looks nothing like this. DO NOT use this code.
Instead, read/run it to understand how this specific network works.
'''
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()
self.epochs = []
self.losses = []
def setFeatures(self, w1,w2,w3,w4,w5,w6,b1,b2,b3):
# Weights
self.w1 = w1
self.w2 = w2
self.w3 = w3
self.w4 = w4
self.w5 = w5
self.w6 = w6
# Biases
self.b1 = b1
self.b2 = b2
self.b3 =b3
def feedforward(self, x):
# x is a numpy array with 2 elements.
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 # number of times to loop through the entire dataset
for epoch in range(epochs):
for x, y_true in zip(data, all_y_trues):
# --- Do a feedforward (we'll need these values later)
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
# --- Calculate partial derivatives.
# --- Naming: d_L_d_w1 represents "partial L / partial w1"
d_L_d_ypred = 2 * (y_pred - y_true)
# 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)
# --- Update weights and biases
# 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
# --- Calculate total loss at the end of each epoch
if epoch % 100 == 0:
#操作數據的列方向,x[0] = [133,160,152,...] x[1] = [165,182,177]
y_preds = np.apply_along_axis(self.feedforward, 1, data)
loss = mse_loss(y_preds, all_y_trues)
self.epochs.append(epoch)
self.losses.append(loss)
print("Epoch %d loss: %.3f" % (epoch, loss))
# Train our neural network!
network = OurNeuralNetwork()
network.train(data, all_y_trues)
損失函數收斂曲線:
用訓練的模型進行預測:
# Make some predictions
emily = np.array([47, 11]) #
frank = np.array([-23, -5]) #
print("Emily: %.3f" % network.feedforward(emily)) # 0.951 - F
print("Frank: %.3f" % network.feedforward(frank)) # 0.039 - M
Emily: 0.957
Frank: 0.030
待梳理的內容
- 對數據的預處理能夠極大的改進你的模型,使得訓練的結果更符合預期
- 損失函數和激活函數的選擇