神經網絡學習(四)

神經網絡學習(四)

上一篇我們推導和實驗了使用交叉熵作爲損失函數能夠明顯提高神經網絡訓練的精度和速度,但是爲什麼呢?這一篇就結合我再在網上看到的資料進行一個探索。

假設

  • 樣本維度是2維
  • 分類結果有三類
  • 一種兩個樣本(x11,x12,y1),(x21,x22,y2)(x_{11},x_{12},y_1),(x_{21},x_{22},y_2)

公式推導

前向傳播

和之前的推導類似,前向傳播公式:
輸出層使用softmax的公式:
z1=ω11x1+ω12x2+b1z2=ω21x1+ω22x2+b2z3=ω31x1+ω32x2+b3a1=ez1ez1+ez2+ez3a2=ez2ez1+ez2+ez3a3=ez3ez1+ez2+ez3 \begin{aligned} z_1 &= \omega_{11}x_1+ \omega_{12}x_2+b_1 \\ z_2 &= \omega_{21}x_1+ \omega_{22}x_2+b_2 \\ z_3 &= \omega_{31}x_1+ \omega_{32}x_2+b_3 \\ a_1 &= \frac{e^{z_1}}{e^{z_1}+e^{z_2}+e^{z_3}} \\ a_2 &= \frac{e^{z_2}}{e^{z_1}+e^{z_2}+e^{z_3}} \\ a_3 &= \frac{e^{z_3}}{e^{z_1}+e^{z_2}+e^{z_3}} \\ \end{aligned}
輸出層使用sigmoid的公式:
z1=ω11x1+ω12x2+b1z2=ω21x1+ω22x2+b2z3=ω31x1+ω32x2+b3a1=11+ez1a2=11+ez2a3=11+ez3 \begin{aligned} z_1 &= \omega_{11}x_1+ \omega_{12}x_2+b_1 \\ z_2 &= \omega_{21}x_1+ \omega_{22}x_2+b_2 \\ z_3 &= \omega_{31}x_1+ \omega_{32}x_2+b_3 \\ a_1 &= \frac{1}{1+e^{z_1}} \\ a_2 &= \frac{1}{1+e^{z_2}} \\ a_3 &= \frac{1}{1+e^{z_3}} \\ \end{aligned}
使用交叉熵損失函數:
E=y1loga1+y2loga2+y3loga3 \begin{aligned} E=y_1\text{log} a_1+y_2\text{log} a_2 +y_3\text{log}a_3 \end{aligned}
使用平方誤差損失函數:
E=12[(y1a1)2+(y1a1)2+(y1a1)2] \begin{aligned} E=\frac{1}{2}\left[(y_1-a_1)^2+(y_1-a_1)^2+(y_1-a_1)^2\right] \end{aligned}

反向傳播

我們反向傳播是爲了看損失函數對神經網絡的權值的影響,因此這裏只求對ω11\omega_{11}的影響,別的可以以此類推
對於平方誤差損失函數,有我之前神經網絡學習(二)的結論可以寫出:
Eω11=(y1a1)a1(1a1)x11 \begin{aligned} \frac{\partial E}{\partial \omega_{11}}=-(y_1-a_1)a_1(1-a_1)x_{11} \end{aligned}
**我們要明確,在使用平方誤差損失函數時我們的目的是什麼?**我們是想知道ω11\nabla \omega_{11}和誤差y1a1y_1-a_1的關係,即ω11=f(y1a1)\nabla \omega_{11}=f(y_1-a_1)
我們知道y1y_1的取值只能是0或1,而a_1的取值範圍是010\sim 1
A=y1a1A=|y_1-a_1|
y1=1y_1=1時,A=y1a1A=y_1-a_1a1=y1Aa_1=y_1-A帶入上式得
Eω11=A(y1A)(1(y1A))x11=A2(1A)x11 \begin{aligned} \frac{\partial E}{\partial \omega_{11}}=-A(y_1-A)(1-(y_1-A))x_{11}=-A^2(1-A)x_{11} \end{aligned}
y1=0y_1=0時,A=a1A=a_1,帶入上式得
Eω11=(A)A(1A)x11=A2(1A)x11 \begin{aligned} \frac{\partial E}{\partial \omega_{11}}=-(-A)A(1-A)x_{11}=A^2(1-A)x_{11} \end{aligned}
上面的推導中x11x_{11}是樣本,在訓練中是常量,只有誤差A是變量,因此A2(1A)A^2(1-A)是參數更新的因素

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

A = np.linspace(0, 1, 100)
plt.plot(A, A ** 2 * (1 - A))
plt.xlabel("|error|")
plt.ylabel("$\delta w_{11}$")
plt.title("$\delta w_{11}$=f(A)")
plt.show()

在這裏插入圖片描述
從上圖可以看出,當誤差不斷增大的時候,更新的參數先變大後變小,也就是說當誤差太大的時候,反向傳播變慢,乃至可能停止,這個是跟我們的期望相反,我們期望的是向誤差小的地方移動,並且誤差越大移動越快。
對於交叉熵損失函數,有我之前神經網絡學習(三)的結論可以寫出:
Eω11=a1y1 \begin{aligned} \frac{\partial E}{\partial \omega_{11}}=a_1-y_1 \end{aligned}
y1=1y_1=1時,A=y1a1A=y_1-a_1a1=y1Aa_1=y_1-A帶入上式得:
Eω11=A \begin{aligned} \frac{\partial E}{\partial \omega_{11}}=-A \end{aligned}
y1=0y_1=0時,A=a1A=a_1,帶入上式得:
Eω11=A \begin{aligned} \frac{\partial E}{\partial \omega_{11}}=A \end{aligned}
此時,就可以之間看出,交叉熵損失函數中誤差和參數更新完全是線性關係,服務我們對參數更新的期望。

實驗驗證

對剛剛的分析僅僅是理論的分析,下面可以把實驗中的數據打印出來,驗證我們的猜想。

交叉熵代價函數

import numpy as np
from sklearn.datasets import load_digits  #導入手寫數字數據集
from sklearn.preprocessing import LabelBinarizer  # 標籤二值化
from sklearn.model_selection import train_test_split  # 切割數據,交叉驗證法
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1 / (1 + np.exp(-x))


def dsigmoid(x):
    return x * (1 - x)

def softmax(x):
    sum = 0
    temp = np.zeros(len(x[0]))
    for i in range(len(x[0])):
        sum += np.exp(x[0][i]) 
        
    for i in range(len(x[0])):
        temp[i] = np.exp(x[0][i]) / sum 
        
    temp = np.atleast_2d(temp)
    return temp 
    

class NeuralNetwork:
    def __init__(self, layers):  # (64,100,10)
        # 權重的初始化,範圍-1到1:+1的一列是偏置值
        self.V = np.random.random((layers[0] + 1, layers[1] + 1)) * 2 - 1
        self.W = np.random.random((layers[1] + 1, layers[2])) * 2 - 1

    def train(self, X, y, lr=0.11, epochs=10000):
        # 添加偏置值:最後一列全是1
        temp = np.ones([X.shape[0], X.shape[1] + 1])
        temp[:, 0:-1] = X
        X = temp

        for n in range(epochs + 1):
            # 在訓練集中隨機選取一行(一個數據):randint()在範圍內隨機生成一個int類型
            i = np.random.randint(X.shape[0])
            x = [X[i]]
            # 轉爲二維數據:由一維一行轉爲二維一行
            x = np.atleast_2d(x)

            # L1:輸入層傳遞給隱藏層的值;輸入層64個節點,隱藏層100個節點
            # L2:隱藏層傳遞到輸出層的值;輸出層10個節點
            L1 = sigmoid(np.dot(x, self.V))
            L2 = softmax(np.dot(L1, self.W))
            # L2_delta:輸出層對隱藏層的誤差改變量
            # L1_delta:隱藏層對輸入層的誤差改變量
            Error.append(np.abs(y[i][0] - L2[0][0]))
            L2_delta = y[i] - L2
            gradient.append(L2_delta[0][0])
            L1_delta = L2_delta.dot(self.W.T) * dsigmoid(L1)
            #print(L2)
            # 計算改變後的新權重
            self.W += lr * L1.T.dot(L2_delta)
            self.V += lr * x.T.dot(L1_delta)
            if n > 40000:
                lr = lr * 0.99
            # 每訓練1000次輸出一次準確率
            if n % 1000 == 0:
                predictions = []
                for j in range(X_test.shape[0]):
                    # 獲取預測結果:返回與十個標籤值逼近的距離,數值最大的選爲本次的預測值
                    o = self.predict(X_test[j])
                    # 將最大的數值所對應的標籤返回
                    predictions.append(np.argmax(o))
                # np.equal():相同返回true,不同返回false
                accuracy = np.mean(np.equal(predictions, y_test))
                print('迭代次數:', n, '準確率:', accuracy)

    def predict(self, x):
        # 添加偏置值:最後一列全是1
        temp = np.ones([x.shape[0] + 1])
        temp[0:-1] = x
        x = temp
        # 轉爲二維數據:由一維一行轉爲二維一行
        x = np.atleast_2d(x)

        # L1:輸入層傳遞給隱藏層的值;輸入層64個節點,隱藏層100個節點
        # L2:隱藏層傳遞到輸出層的值;輸出層10個節點
        L1 = sigmoid(np.dot(x, self.V))
        L2 = softmax(np.dot(L1, self.W))
        return L2
# 載入數據:8*8的數據集
digits = load_digits()
X = digits.data
Y = digits.target
# 輸入數據歸一化:當數據集數值過大,乘以較小的權重後還是很大的數,代入sigmoid激活函數就趨近於1,不利於學習
X -= X.min()
X /= X.max()

NN = NeuralNetwork([64, 80, 10])
# sklearn切分數據
X_train, X_test, y_train, y_test = train_test_split(X, Y)
# 標籤二值化:將原始標籤(十進制)轉爲新標籤(二進制)
labels_train = LabelBinarizer().fit_transform(y_train)
labels_test = LabelBinarizer().fit_transform(y_test)
global Error 
global gradient 
Error = []
gradient = []
print('開始訓練')
NN.train(X_train, labels_train, epochs=40000)
print('訓練結束')
plt.plot(Error, gradient,".")

在這裏插入圖片描述
可以看出誤差和梯度是標準的線性關係,並且是其實在誤差特別大的點並不是很多。

平方誤差代價函數

import numpy as np
from sklearn.datasets import load_digits  #導入手寫數字數據集
from sklearn.preprocessing import LabelBinarizer  # 標籤二值化
from sklearn.model_selection import train_test_split  # 切割數據,交叉驗證法
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1 / (1 + np.exp(-x))


def dsigmoid(x):
    return x * (1 - x)

def softmax(x):
    sum = 0
    temp = np.zeros(len(x[0]))
    for i in range(len(x[0])):
        sum += np.exp(x[0][i]) 
        
    for i in range(len(x[0])):
        temp[i] = np.exp(x[0][i]) / sum 
        
    temp = np.atleast_2d(temp)
    return temp 
    

class NeuralNetwork:
    def __init__(self, layers):  # (64,100,10)
        # 權重的初始化,範圍-1到1:+1的一列是偏置值
        self.V = np.random.random((layers[0] + 1, layers[1] + 1)) * 2 - 1
        self.W = np.random.random((layers[1] + 1, layers[2])) * 2 - 1

    def train(self, X, y, lr=0.11, epochs=10000):
        # 添加偏置值:最後一列全是1
        temp = np.ones([X.shape[0], X.shape[1] + 1])
        temp[:, 0:-1] = X
        X = temp

        for n in range(epochs + 1):
            # 在訓練集中隨機選取一行(一個數據):randint()在範圍內隨機生成一個int類型
            i = np.random.randint(X.shape[0])
            x = [X[i]]
            # 轉爲二維數據:由一維一行轉爲二維一行
            x = np.atleast_2d(x)

            # L1:輸入層傳遞給隱藏層的值;輸入層64個節點,隱藏層100個節點
            # L2:隱藏層傳遞到輸出層的值;輸出層10個節點
            L1 = sigmoid(np.dot(x, self.V))
            L2 = sigmoid(np.dot(L1, self.W))

            # L2_delta:輸出層對隱藏層的誤差改變量
            # L1_delta:隱藏層對輸入層的誤差改變量
            L2_delta = (y[i] - L2) * dsigmoid(L2)
            L1_delta = L2_delta.dot(self.W.T) * dsigmoid(L1)
            Error.append(np.abs(y[i][0] - L2[0][0]))
            gradient.append(L2_delta[0][0])
            #print(L2)
            # 計算改變後的新權重
            self.W += lr * L1.T.dot(L2_delta)
            self.V += lr * x.T.dot(L1_delta)
            if n > 40000:
                lr = lr * 0.99
            # 每訓練1000次輸出一次準確率
            if n % 1000 == 0:
                predictions = []
                for j in range(X_test.shape[0]):
                    # 獲取預測結果:返回與十個標籤值逼近的距離,數值最大的選爲本次的預測值
                    o = self.predict(X_test[j])
                    # 將最大的數值所對應的標籤返回
                    predictions.append(np.argmax(o))
                # np.equal():相同返回true,不同返回false
                accuracy = np.mean(np.equal(predictions, y_test))
                print('迭代次數:', n, '準確率:', accuracy)

    def predict(self, x):
        # 添加偏置值:最後一列全是1
        temp = np.ones([x.shape[0] + 1])
        temp[0:-1] = x
        x = temp
        # 轉爲二維數據:由一維一行轉爲二維一行
        x = np.atleast_2d(x)

        # L1:輸入層傳遞給隱藏層的值;輸入層64個節點,隱藏層100個節點
        # L2:隱藏層傳遞到輸出層的值;輸出層10個節點
        L1 = sigmoid(np.dot(x, self.V))
        L2 = softmax(np.dot(L1, self.W))
        return L2
# 載入數據:8*8的數據集
digits = load_digits()
X = digits.data
Y = digits.target
# 輸入數據歸一化:當數據集數值過大,乘以較小的權重後還是很大的數,代入sigmoid激活函數就趨近於1,不利於學習
X -= X.min()
X /= X.max()

NN = NeuralNetwork([64, 80, 10])
# sklearn切分數據
X_train, X_test, y_train, y_test = train_test_split(X, Y)
# 標籤二值化:將原始標籤(十進制)轉爲新標籤(二進制)
labels_train = LabelBinarizer().fit_transform(y_train)
labels_test = LabelBinarizer().fit_transform(y_test)
global Error 
global gradient 
Error = []
gradient = []
print('開始訓練')
NN.train(X_train, labels_train, epochs=40000)
print('訓練結束')
plt.plot(Error, gradient,".")

在這裏插入圖片描述
此時就可以看到,誤差和梯度不是一個線性的變化,而且誤差分佈在較大的地方的密集程度是比交叉熵的明顯增多的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章