1. 程式人生 > >單隱層前饋神經網路

單隱層前饋神經網路

這篇部落格主要介紹神經網路基礎,單隱層前饋神經網路與反向傳播演算法。

神經網路故名思議是由人的神經系統啟發而得來的一種模型。神經網路可以用來做分類和迴歸等任務,其具有很好的非線性擬合能力。接下來我們就來詳細介紹一下但隱層前饋神經網路。

首先我們來看一下神經元的數學模型,如下圖所示:

可以看到x_1, x_2, ..., x_m為輸入訊號,而神經元最終輸出為y,由此我們可以看到,單個神經元是多輸入單輸出的。但是從上圖我們可以看到,有輸入到輸出中間還經歷了一些步驟,這些步驟在神經網路中是非常關鍵的,因為正是有了中間這些步驟,一個神經網路才能夠真正的具有了非線性擬合能力,這是為什麼呢?接下來就給出解釋。

從上圖中輸入x_1, x_2, ..., x_m

首先遇到了權值w_1, w_2, ..., w_m做了一個加權求和的操作,並對加權求和的結果加入了偏置b,得到了輸出v,如上圖求和節點所示,具體數學公式如下所示:

v = b + \sum_{i=1}^{m}x_iw_i

到這一步還沒有使得神經網路具有非線性擬合能力,因為正如我們所看到的,所有的操作對於輸入來說都是線性的,使得神經網路具有非線性擬合能力的一步是下一步操作,如上圖中啟用函式處所示,我們將線性加權求和得到的輸出v輸入到了一個叫做啟用函式的東西里面,那麼啟用函式又是什麼呢?實際上啟用函式就是一個非線性的一個函式,如上圖中\varphi (\cdot )就是啟用函式,那麼常用的啟用函式有以下幾種:

\left\{\begin{matrix} sigmoid(x)=\frac{1}{1+e^{-x}}\\ tanh(x)=\frac{e^x-e^{-x}}{e^x+e^{-x}}\\ relu(x) = max(0,x) \end{matrix}\right.

對於前兩個啟用函式,sigmoid啟用函式將輸入值壓縮到了0到1之間,但其缺點是存在梯度消失的問題,如下圖所示:

可以看到當sigmoid的輸入很大或很小時,其梯度幾乎趨近於0。

再來看tanh啟用函式,如下圖所示:

由上圖可以看出,tanh啟用函式和sigmoid啟用函式非常相似,只不過tanh啟用函式將輸入值壓縮到了-1到1之間,由其梯度影象可以看出,當輸入很大或很小時梯度也是趨於0的。

最後讓我們來看一下relu啟用函式,如下圖所示:

從上圖中可以看出,relu啟用函式在0到正無窮上會隨著輸入的增大而無限增大,在小於0的區間上其值全為0,但是我們還可以看到其梯度在0到正無窮上永遠為1。

由於神經網路最後對於損失函式的優化也是使用梯度下降,因此在實際運用中,我們基本上會經常用relu啟用函式,因為sigmoid啟用函式以及tanh啟用函式從上面的影象中已經可以看出,其存在梯度消失的問題,我們知道,當我們使用梯度下降去優化某個損失函式時,是需要求梯度的,但是如果輸入過大(可能初始化權重過大,也可能特徵過大)並且採取的是sigmoid或tanh啟用函式,那麼當我們在輸入位置求取梯度時其梯度值趨近於0,而我們對於引數的更新量也趨近於0,因此最終會因為梯度消失的問題造成收斂過慢,因此我們會使用relu啟用函式來加快收斂速度,但relu啟用函式的精度不如sigmoid啟用函式。

接下來我們看一下單隱層前饋神經網路一般結構,如下圖:

其中x_m^{(n)}表示第m個樣本的第n個特徵,可以看到輸入層神經元神經元個數應該和一個樣本的特徵數一樣,而y_m^{(k)}表示第m個樣本的第k個輸出,通常情況下,如果這是一個分類問題則k\geq 2,如果是迴歸問題則k= 1

下面舉一個結構比較簡單的網路來說明但隱層前饋網路的工作機理,網路結構如下圖:

上圖中w_l^{ij}表示第l層第i個神經元和第l+1層的第j個神經元連結權重。則隱層神經元輸出計算公式如下:

h_1^{(1)} = \varphi ^{(1)}(x_m^{(1)}*w_1^{11}+x_m^{(2)}*w_1^{21}+x_m^{(3)}*w_1^{31}+b_1^{(1)})

h_2^{(1)} = \varphi ^{(1)}(x_m^{(1)}*w_1^{12}+x_m^{(2)}*w_1^{22}+x_m^{(3)}*w_1^{32}+b_2^{(1)})

其中h_j^{(i)}表示第i隱層的第j個神經元的輸出。而\varphi^{(i)}表示第i隱層神經元輸出時所用的啟用函式,b^{(i)}_j表示第i隱層的第j個神經元的計算線性加權求和時所用的偏置,由於是但隱層神經網路,所以上述所說的第i隱層就可以直接理解為隱層即可。

而輸出層神經元的輸出計算公式如下:

y=\varphi ^{(2)}(h_1^{(1)}*w_2^{11}+h_2^{(1)}*w_2^{21}+b^{(2)}_1)

當我們求得輸出之後就能夠計算輸出與真實之之間的差距,我們稱為損失,我們往往是送入一批資料,所以會求得一批損失,這裡我們以迴歸問題為例求取均方誤差:

loss = \frac{1}{n}\sum_{i=1}^{n}(y_i^t-y_i^p)^2

其中y_i^p為網路輸出,而y_i^t為樣本真實標記。

當我們求得損失函式後,我們就可以對各個引數求偏導計算梯度,進而進行梯度下降來優化損失函式。

注意如果是迴歸問題最後輸出層不進行啟用函式的啟用操作,因為如果採用sigmoid或tanh啟用函式啟用後輸出永遠在0到1區間內或-1到1區間內,而如果採取relu啟用函式啟用,則輸出捨棄了負值的那一部分,這樣可能永遠都不能很好的擬合真實值;而如果是分類問題便可以使用啟用函式,並且如果是分類問題,我們的輸出層神經元個數同類別數目是一樣的,並且我們首先需要將樣本標記進行one_hot編碼,使每個樣本標記是一個概率分佈,即某個類別的概率為1。我們網路輸出之後需要進行一個softmax將網路輸出轉化為一個概率分佈,而我們的損失是求取網路輸出的概率分佈和真實樣本標記的交叉熵(交叉熵可以衡量兩個概率分佈之間的距離,我們希望真實的概率分佈和網路輸出的概率分佈距離越小越好),同樣我們往往是求取一批樣本的交叉熵然後取均值作為損失函式,之後同迴歸問題一樣對其進行梯度下降優化即可。

以下是本人用python以及tensorflow庫編寫的但隱層神經網路供大家參考:


# coding: utf-8

# In[1]:


import numpy as np
from numpy import random as rd
import matplotlib.pyplot as plt
import tensorflow as tf


# In[2]:


# 構造樣本點
x = np.linspace(-5, 5, 100)
# 對x做了個升維,樣本變成了[x, x**2, x**3]
x = np.concatenate([x.reshape(-1, 1), np.power(x.reshape(-1, 1), 2),
                    np.power(x.reshape(-1, 1), 3)], axis=1)
y = (np.sin(x[:, 0]) + rd.uniform(-0.2, 0.2, 100)).reshape(-1, 1)
# unit_count_of_hidden_layer是指定隱層神經元個數的,max_train_step表示訓練多少次,
# learning_rate表示梯度下降的學習率
unit_count_of_hidden_layer = 10
max_train_step = 2000
learning_rate = 0.1


# In[3]:


def add_layer(layer_number, input_value, unit_count, activation_function):
    # 用來新增隱層和輸出層
    # input_value是隱層的輸入
    # unit_count是隱層的神經元個數
    # activation_function指定啟用函式:tf.nn.relu或tf.nn.sigmoid或tf.nn.tanh
    with tf.variable_scope("layer_%d" % layer_number, reuse=tf.AUTO_REUSE):
        weight = tf.get_variable(shape=[input_value.shape[-1], unit_count], 
                                 dtype=tf.float32, initializer=tf.random_normal_initializer(mean=1, stddev=1), 
                                 name="weights", trainable=True)
        bias = tf.get_variable(shape=[1, unit_count], dtype=tf.float32, initializer=tf.constant_initializer(0),
                               name="bias", trainable=True)
        matmul_add_bias_result = tf.add(tf.matmul(input_value, weight), bias, name="matmul_add_bias")
        output = matmul_add_bias_result
        if activation_function:
            output = activation_function(matmul_add_bias_result)
        return output
            
def demo():
    x_placeholder = tf.placeholder(shape=[100, x.shape[-1]], dtype=tf.float32, name="x_input")
    y_placeholder = tf.placeholder(shape=[100, 1], dtype=tf.float32, name="y_input")
    hidden_layer_output = add_layer(1, x_placeholder, unit_count_of_hidden_layer, tf.nn.sigmoid)
    y_output = add_layer(2, hidden_layer_output, 1, None)
    loss = tf.reduce_mean(tf.square(y_output - y_placeholder))
    optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
    train_op = optimizer.minimize(loss)
    init_op = tf.global_variables_initializer()
    with tf.Session(config=tf.ConfigProto(allow_soft_placement=True)) as sess:
        sess.run(init_op)
        for i in range(1, 1 + max_train_step):
            _, loss_, y_output_ = sess.run(fetches=[train_op, loss, y_output],
                                           feed_dict={x_placeholder: x, y_placeholder: y})
            if i % int(0.1 * max_train_step) == 0:
                print("第%d次迭代損失:%f" % (i, loss_))
    return y_output_


# In[4]:


y_predict = demo()


# In[5]:


# 畫圖
fig = plt.figure(figsize=(8, 6))
ax = plt.subplot(1, 1, 1)
ax.scatter(x[:, 0], y, color="r", label="samples")
ax.plot(x[:, 0], y_predict, linewidth=1, label="predict", color="b")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.legend(loc="upper right")
plt.show()