神經網絡基礎1:實現一個簡單的神經網絡

線性回歸網絡

神經網絡基礎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. 準備數據

我們要訓練的數據如下(性別男用1表示,女用0表示):

  1. 確定要使用的損失函數
    在訓練神經網絡之前,我們需要有一個標準定義它到底好不好,以便我們進行改進,這就是損失(loss)。
  1. 求各個權重和偏置的偏導數

神經網絡基礎: 線性回歸理論實現章節我們已經討論過損失函數的意義,如果要使得損失函數求得最小值,我們需要運用梯度下降法不斷更新各個變量的值,直到達到穩定:

而在我們現在的場景中,損失函數是包含以下九個變量的函數,不過都符合以上變量更新公式

因此為了迭代更新L,使得其值不斷減小,我們需要求出各個變量的偏導數。

在求各個變量的偏導數之前,我們先求出,在輸入值x = [x0,x1] = [weight,height]已知的情況下,各個神經元節點的輸出表達公式; 因為這些表達式,在計算偏導數的時候會用到

各個神經元的輸出表達式:

上文已經說過,每個神經元的輸出,都需要經過激活函數的轉換處理,將其中從[-∞,∞]轉換到[0,1],已符合我們的值的輸出預期



在f(x)的基礎上,我們可以求出

前饋過程

以上計算過程中,各個神經元的輸入等于各個前一層神經元的加權和,這個過程被稱為神經網絡的前饋過程

各個神經元的偏導數

已知損失函數的表達式如下:


其中,N表示樣本數據的大小,當我們對單個樣本數據計算損失函數時,其表達式為:

我們先求得求得L對ypred的導數:


根據鏈式求導法則:


因此,如果要求得各個變量參數的偏導數,我們從后向前求總各個參數的偏導數:

各個神經元的偏導數.gif

這種從后之前求各個神經元上輸入變量的的偏導數的過程,稱為神經網絡的后饋過程

  1. 梯度下降更新每個權重和偏置
    通過以上公式,求出所有權重和偏置后,就可以通過梯度下降法,更新每個權重和偏置
梯度下降

總結下訓練過程:
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

待梳理的內容

  1. 對數據的預處理能夠極大的改進你的模型,使得訓練的結果更符合預期
  2. 損失函數和激活函數的選擇

github工程

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容