神經(jīng)網(wǎng)絡(luò)基礎(chǔ)1:實(shí)現(xiàn)一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)

線(xiàn)性回歸網(wǎng)絡(luò)

神經(jīng)網(wǎng)絡(luò)基礎(chǔ)0:線(xiàn)性邏輯回歸理論實(shí)現(xiàn)章節(jié),我們通過(guò)對(duì)y = wx+b的預(yù)測(cè),實(shí)現(xiàn)了一個(gè)最簡(jiǎn)單的線(xiàn)性回歸模型;線(xiàn)性回歸模型也是最簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)模型,只有一個(gè)輸入?yún)?shù),一個(gè)神經(jīng)元節(jié)點(diǎn)和一個(gè)輸出參數(shù)

線(xiàn)性回歸

神經(jīng)元(Neurons),它是神經(jīng)網(wǎng)絡(luò)的基本單元。神經(jīng)元先獲得輸入,然后執(zhí)行某些數(shù)學(xué)運(yùn)算后,再產(chǎn)生一個(gè)輸出。

在這個(gè)神經(jīng)元中,輸入總共經(jīng)歷了2步數(shù)學(xué)運(yùn)算
先將一個(gè)輸入乘以權(quán)重(weight):
x→x × w,

再加上一個(gè)偏置(bias):
x × w+b

得到最后的結(jié)果:y = x × w+b

這算是最簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)了,只有一個(gè)神經(jīng)元,并且沒(méi)有激活函數(shù)對(duì)其進(jìn)行處理(因?yàn)檩敵鰕是連續(xù)的情況下,輸出層的激活函數(shù)可以使用線(xiàn)性函數(shù)y = x)

接下來(lái),我們將根據(jù)一組真實(shí)的的場(chǎng)景數(shù)據(jù),搭建一個(gè)具備兩個(gè)神經(jīng)元的網(wǎng)絡(luò)

神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)方法

我們有以上身高,體重,和性別數(shù)據(jù),接下來(lái)將搭建一個(gè)神經(jīng)網(wǎng)絡(luò),能夠根據(jù)輸入的體重和身高,預(yù)測(cè)該群眾屬于什么性別

基本模塊搭建-神經(jīng)元

神經(jīng)元(Neurons),它是神經(jīng)網(wǎng)絡(luò)的基本單元。神經(jīng)元先獲得輸入,然后執(zhí)行某些數(shù)學(xué)運(yùn)算后,再產(chǎn)生一個(gè)輸出。以下是一個(gè)2輸入神經(jīng)元的例子:

在這個(gè)神經(jīng)元中,輸入總共經(jīng)歷了3步數(shù)學(xué)運(yùn)算,

先將兩個(gè)輸入乘以權(quán)重(weight):
x1→x1 × w1
x2→x2 × w2

把兩個(gè)結(jié)果想加,再加上一個(gè)偏置(bias):
(x1 × w1)+(x2 × w2)+ b

上式的線(xiàn)性輸出區(qū)間為整個(gè)實(shí)數(shù)范圍,而我們預(yù)測(cè)要求輸出范圍在[0,1]之間,所以還需要使用激活函數(shù)對(duì)上式的線(xiàn)性函數(shù)輸出進(jìn)行處理(之所以使用激活函數(shù),是因?yàn)樯砀吆腕w重的的數(shù)據(jù)范圍是不一樣的,如果直接使用原始的數(shù)據(jù)輸出,那么他們對(duì)性別的影響程度的影響程度將是不一樣的;上文中,我們直接將身高和體重直接減去一個(gè)固定值,也是相當(dāng)于使用了一個(gè)激活函數(shù),只不過(guò)這個(gè)激活函數(shù)是線(xiàn)性的(y = x - b),實(shí)際使用中,需要使用非線(xiàn)性函數(shù)對(duì)每個(gè)神經(jīng)元的輸出進(jìn)行修正, 關(guān)于激活函數(shù)我們?yōu)樵趩为?dú)的章節(jié)中進(jìn)行討論)

y = f(x1 × w1 + x2 × w2 + b)

一種常用的激活函數(shù)是sigmoid函數(shù):

sigmoid函數(shù)的輸出介于0和1,我們可以理解為它把 (?∞,+∞) 范圍內(nèi)的數(shù)壓縮到 (0, 1)以?xún)?nèi)。正值越大輸出越接近1,負(fù)向數(shù)值越大輸出越接近0

為了計(jì)算方便,我們將輸入值統(tǒng)一減去一個(gè)常量:(因?yàn)楫?dāng)輸出值很大的時(shí)候,激活函數(shù)的斜率(梯度)很小。因此,在這個(gè)區(qū)域內(nèi),梯度下降算法會(huì)運(yùn)行得比較慢。在實(shí)際應(yīng)用中,應(yīng)盡量避免是的輸出落在這個(gè)區(qū)域,使輸出盡可能限定在零值附近,從而提高梯度下降算法運(yùn)算速度

舉個(gè)例子,上面神經(jīng)元里的權(quán)重和偏置取如下數(shù)值:

w=[0,1]
b = 4

w=[0,1]是w1=0、w2=1的向量形式寫(xiě)法。給神經(jīng)元一個(gè)輸入(張二的身高體重?cái)?shù)據(jù))x=[27,17],可以用向量點(diǎn)積的形式把神經(jīng)元的輸出計(jì)算出來(lái):

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

搭建神經(jīng)網(wǎng)絡(luò)

神經(jīng)網(wǎng)絡(luò)就是把一堆神經(jīng)元連接在一起,下面是一個(gè)根據(jù)我們的數(shù)據(jù)搭建的簡(jiǎn)單神經(jīng)網(wǎng)絡(luò)的:

這個(gè)網(wǎng)絡(luò)有2個(gè)輸入(分別代表身高和年齡)、一個(gè)包含2個(gè)神經(jīng)元的隱藏層(h1和h2)、包含1個(gè)神經(jīng)元的輸出層o1。

隱藏層是夾在輸入輸入層和輸出層之間的部分,一個(gè)神經(jīng)網(wǎng)絡(luò)可以有多個(gè)隱藏層;把神經(jīng)元的輸入向前傳遞獲得輸出的過(guò)程稱(chēng)為前饋(feedforward)

我們假設(shè)上面的網(wǎng)絡(luò)里所有神經(jīng)元都具有相同的權(quán)重w=[0,1]和偏置b=0,激活函數(shù)都是sigmoid,那么我們會(huì)得到什么輸出呢?

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

訓(xùn)練神經(jīng)網(wǎng)絡(luò)

現(xiàn)在我們已經(jīng)學(xué)會(huì)了如何搭建神經(jīng)網(wǎng)絡(luò),現(xiàn)在我們來(lái)學(xué)習(xí)如何訓(xùn)練它。

  1. 準(zhǔn)備數(shù)據(jù)

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

  1. 確定要使用的損失函數(shù)
    在訓(xùn)練神經(jīng)網(wǎng)絡(luò)之前,我們需要有一個(gè)標(biāo)準(zhǔn)定義它到底好不好,以便我們進(jìn)行改進(jìn),這就是損失(loss)。
  1. 求各個(gè)權(quán)重和偏置的偏導(dǎo)數(shù)

神經(jīng)網(wǎng)絡(luò)基礎(chǔ): 線(xiàn)性回歸理論實(shí)現(xiàn)章節(jié)我們已經(jīng)討論過(guò)損失函數(shù)的意義,如果要使得損失函數(shù)求得最小值,我們需要運(yùn)用梯度下降法不斷更新各個(gè)變量的值,直到達(dá)到穩(wěn)定:

而在我們現(xiàn)在的場(chǎng)景中,損失函數(shù)是包含以下九個(gè)變量的函數(shù),不過(guò)都符合以上變量更新公式

因此為了迭代更新L,使得其值不斷減小,我們需要求出各個(gè)變量的偏導(dǎo)數(shù)。

在求各個(gè)變量的偏導(dǎo)數(shù)之前,我們先求出,在輸入值x = [x0,x1] = [weight,height]已知的情況下,各個(gè)神經(jīng)元節(jié)點(diǎn)的輸出表達(dá)公式; 因?yàn)檫@些表達(dá)式,在計(jì)算偏導(dǎo)數(shù)的時(shí)候會(huì)用到

各個(gè)神經(jīng)元的輸出表達(dá)式:

上文已經(jīng)說(shuō)過(guò),每個(gè)神經(jīng)元的輸出,都需要經(jīng)過(guò)激活函數(shù)的轉(zhuǎn)換處理,將其中從[-∞,∞]轉(zhuǎn)換到[0,1],已符合我們的值的輸出預(yù)期



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

前饋過(guò)程

以上計(jì)算過(guò)程中,各個(gè)神經(jīng)元的輸入等于各個(gè)前一層神經(jīng)元的加權(quán)和,這個(gè)過(guò)程被稱(chēng)為神經(jīng)網(wǎng)絡(luò)的前饋過(guò)程

各個(gè)神經(jīng)元的偏導(dǎo)數(shù)

已知損失函數(shù)的表達(dá)式如下:


其中,N表示樣本數(shù)據(jù)的大小,當(dāng)我們對(duì)單個(gè)樣本數(shù)據(jù)計(jì)算損失函數(shù)時(shí),其表達(dá)式為:

我們先求得求得L對(duì)ypred的導(dǎo)數(shù):


根據(jù)鏈?zhǔn)角髮?dǎo)法則:


因此,如果要求得各個(gè)變量參數(shù)的偏導(dǎo)數(shù),我們從后向前求總各個(gè)參數(shù)的偏導(dǎo)數(shù):

各個(gè)神經(jīng)元的偏導(dǎo)數(shù).gif

這種從后之前求各個(gè)神經(jīng)元上輸入變量的的偏導(dǎo)數(shù)的過(guò)程,稱(chēng)為神經(jīng)網(wǎng)絡(luò)的后饋過(guò)程

  1. 梯度下降更新每個(gè)權(quán)重和偏置
    通過(guò)以上公式,求出所有權(quán)重和偏置后,就可以通過(guò)梯度下降法,更新每個(gè)權(quán)重和偏置
梯度下降

總結(jié)下訓(xùn)練過(guò)程:
1、從數(shù)據(jù)集中選擇一個(gè)樣本;
2、確定和定義要使用的損失函數(shù)
3、計(jì)算每個(gè)神經(jīng)元的輸出表達(dá)式,并計(jì)算損失函數(shù)對(duì)所有權(quán)重和偏置的偏導(dǎo)數(shù);
4、使用更新公式更新每個(gè)權(quán)重和偏置;
5、回到第1步。

實(shí)現(xiàn)代碼為:

import numpy as np

#激活函數(shù)
def sigmoid(x):
  # Our activation function: f(x) = 1 / (1 + e^(-x))
  return 1 / (1 + np.exp(-x))

#激活函數(shù)求導(dǎo)
def deriv_sigmoid(x):
  fx = sigmoid(x)
  return fx * (1 - fx)

#損失函數(shù)
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:
        #操作數(shù)據(jù)的列方向,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)

損失函數(shù)收斂曲線(xiàn):


用訓(xùn)練的模型進(jìn)行預(yù)測(cè):

# 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

待梳理的內(nèi)容

  1. 對(duì)數(shù)據(jù)的預(yù)處理能夠極大的改進(jìn)你的模型,使得訓(xùn)練的結(jié)果更符合預(yù)期
  2. 損失函數(shù)和激活函數(shù)的選擇

github工程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容