Simple Neural Network

Blackteaxx發表於2024-04-27

神經網路——從 PLA 到 BP 神經網路

0. 推薦閱讀

B 站白板推導系列二十三(沒有任何數學推導,能夠看得很舒服)

李沐-動手學深度學習

1. 感知機學習演算法(Perceptron Learning Algorithm)

相信能看到神經網路的朋友對於機器學習的基礎演算法已經瞭解了個大概了,如果你沒有聽說過感知機演算法,那麼你可以將它簡單理解為你很熟悉的一個演算法的弱化版:支援向量機。

感知機學習演算法的原始形態就是對於一個線性可分二分類類別標籤,如何找出一個分離超平面去區分這兩類資料。

說人話:對於二維座標軸上的一堆散點,你能不能找出來一條線,依據類別標籤把它們分成兩類。

假設輸入空間\(X = \{(x_1, x_2)\}\),特徵空間\(Y=\{(+1/-1)\}\),我們有如下幾個例子:

  1. 與關係
    20230504235225

  2. 或關係
    20230504235405

有了這兩個我畫的非常抽象的圖,你可能已經理解什麼叫線性可分,那麼單層感知機演算法的學習模型是什麼呢?

其實就是尋找到\(wx + b = 0\)(這裡的\(w x b\)均是向量)這樣一個分離超平面,是不是非常簡單?

那對於機器學習演算法,跟模型同等重要的是學習策略,即如何取得我們想要的那個最優的模型,由於這章主要講的是神經網路,所以損失函式的推導我就不細說了。最終會得到一個感知機模型的損失函式

\[L(w,b) = -\sum_{誤分類的x}y_i(wx_i + b) \]

而我們的目的是求出 w 與 b,所以可以轉化成一個最最佳化問題(讓損失函式最小化)

\[w,b = argmin_{w,b}L(w,b) \]

那麼這個最最佳化問題,我們可以用梯度下降的演算法(這個有需求我再講,因為我也不懂原理,只知道這樣可以)來求,這邊用的是隨機梯度下降。

\[w \leftarrow w + \eta y_ix_i \\ b \leftarrow b + \eta y_i \]

這樣我們就能求出最優解了!

那麼問題來了,感知機模型要求線性可分線性不可分又是什麼樣的情況?看下面的異或問題

  1. 異或關係
    20230505001701
    從圖上可以看出,你沒有辦法找到一條直線將正負類分離開來,但是存在一條曲線能夠分離。在 1969 有人證明了感知機沒有辦法解決異或問題,這就引出了接下來的多層感知機(神經網路)。

2. 神經網路(多層感知機)

線性不可分的問題,我們有帶核方法的 SVM(Kernel SVM)和神經網路兩種處理手段,Kernel SVM 是出現在神經網路之後的,這個我們不多講,因為我也不懂。

在具體講神經網路為什麼有用之前,我們先理解一下神經網路是如何工作的。

  1. 神經元

神經元其實就是一個感知機,有輸入\(x\),有權值\(w\),有偏置(bias)\(b\)當然你也可以叫它閾值(threshold)\(\theta\),有輸出\(y\),最後就是你可能不太熟悉的啟用函式\(f\)

它的最終表達形式是這樣的:

\[y = f(wx+b) \]

或者

\[y = f(wx-\theta) \]

圖片形式:
20230505002714

這裡的啟用函式,在單層感知機裡就是符號函式,大於 0 取+1,小於 0 取-1。但是由於符號函式不可導,所以啟用函式可以由各種 S 型曲線代替,比如 Sigmoid 函式:
20230505002904

也能起到符號函式的作用,只不過是大於 0.5 取+1,小於 0.5 取-1。

這個啟用函式有什麼作用?就是將線性的作用轉成非線性的作用,這樣多層疊加起來就不再可以被合併為一個線性的作用了,比如:

\[z = f(w_2f(w_1x+b_1) + b_2) \]

如果兩個\(f\)均為\(y=x\),不難得出 z 與 x 是線性的關係。

  1. 神經元疊加

20230505003416

實際上神經元的疊加就是將前一個神經元的輸出作為後一個神經元的輸入,繼續進行加權求和、加偏置、啟用的流程。

圖中的 input layer 即是輸入空間\(X = \{(x_1,x_2,x_3)\}\), hidden layer 為隱藏層,可以不只有一層,就是神經元疊加的層。output layer 為輸出空間,圖上所指為\(Y = \{(y_1,y_2)\}\)

有證明指出超過一層隱藏層的神經網路可以擬合任意函式,這就是神經網路強大的原因。

至於為什麼現在發展的是深度學習(增加隱藏層),而不是廣度學習(增加神經元個數),這就是另外一個問題了。

3. BP 神經網路

在介紹完多層感知機之後,朋友們對神經網路有了個感性認知。第三節講講計算規則簡單的前饋反向傳播(Back Propagation)神經網路

我們知道,感知機學習演算法是基於 Loss Function 的隨機梯度下降法,這也是機器學習的核心思想(應該是)。經典的損失函式就是線性迴歸中的最小二乘法和邏輯迴歸中的交叉熵損失函式(與高斯噪聲 MLE 等價),

而在神經網路中,依賴 Loss Function 的梯度下降依舊是學習演算法的核心,而損失函式的選擇則是依據模型而選擇,在此不詳細說明。

那麼對於具有複雜網路結構的神經網路如何對損失函式梯度下降從而求出引數呢,這就是 BP 神經網路的作用。

BP 神經網路的思想很簡單,就是求導的鏈式法則,目的就是得到 loss function 關於引數 weight 與 bias 的梯度,小例子如下。

第一階段-前向傳播過程
20230505233746

(這張圖與前面的圖不太一樣,不過應該也能看明白)

此圖對應的函式為

\[z = f(wx) \]

z 作為輸出

在向前計算的時候,同步計算好導數\(\frac{\partial z}{\partial x}\)以及\(\frac{\partial z}{\partial \omega}\),並將其儲存下來。(在計算完 loss 有用)

第二階段-反向傳播過程

20230505234518

\[loss = L(\hat{z} - z) \]

在繼續向前得到損失值 loss 的時候,前面的神經元會將\(\frac{\partial loss}{\partial z}\)的值反向傳播給原先的神經元,在計算單元\(f(x,\omega)\)中,將得到的\(\frac{\partial loss}{\partial x}\)與之前儲存的導數相乘,即可得到損失值對於權重以及輸入層的導數,即\(\frac{\partial loss}{\partial x}\),以及\(\frac{\partial loss}{\partial \omega}\)

基於該梯度才進行權重的調整

\[\frac{\partial loss}{\partial w} = \frac{\partial loss}{\partial z} \times \frac{\partial z}{\partial w} \]

更新策略為

\[w \leftarrow w - \eta \frac{\partial loss}{\partial w} \]

其中\(\eta\)為學習率,\(0<\eta<1\)

4. 手工計算 BP

如果你看懂了一個神經元的更新,那麼恭喜你學會了 BP 神經網路!讓我們來做一個練習題(源自 play 老師的 ppt):

請你更新一下權重
20230505234925

20230505235021

20230505235030

真實值

\[Y = \left[\begin{matrix} 0.01 \\ 0.99 \end{matrix} \right] \]

這個圖實際上有些問題,比如\(W_{input-hidden}\)的第一行第三列是 0.4,但是實際上第三個輸入和隱藏層第一個結點沒有連線,我們以權重矩陣為準。

前向

\[X_{hidden} = W_{input-hidden}X = \left[ \begin{matrix} 0.9 & 0.3 & 0.4 \\ 0.2 & 0.8 & 0.2 \\ 0.8 & 0.1 & 0.9 \end{matrix} \right] \left[ \begin{matrix} 0.9 \\ 0.1 \\ 0.8 \end{matrix} \right] = \left[ \begin{matrix} 1.16 \\ 0.42 \\ 0.62 \end{matrix} \right] \]

\[Output_{hidden} = Sigmoid(X_{hidden}) = Sigmoid( \left[ \begin{matrix} 1.16 \\ 0.42 \\ 0.62 \end{matrix} \right]) = \left[ \begin{matrix} 0.761 \\ 0.603 \\ 0.650 \end{matrix} \right] \]

\[X_{output} = W_{hidden-output}Output_{hidden} = \left[ \begin{matrix} 0.3 & 0.7 & 0.5 \\ 0.6 & 0.5 & 0.2 \end{matrix} \right] \left[ \begin{matrix} 0.761 \\ 0.603 \\ 0.650 \end{matrix} \right] = \left[ \begin{matrix} 0.975 \\ 0.888 \end{matrix} \right] \]

\[Output_{output} = Sigmoid(X_{output}) = \left[ \begin{matrix} 0.726 \\ 0.708 \end{matrix} \right] \]

反向
簡單起見,我們只做一個權重值的更新(挑個簡單的,第二層的\(w_{1,1} = 0.3\)

損失函式:

\[L(W) = (Output - Y)^T(Output - Y) = 0.59218 \]

我們的目標是:

\[\frac{\partial L(W)}{\partial w_{1,1}} = \frac{\partial L(W)}{\partial Output_{1}} \frac{\partial Output_{1}}{\partial X_{output1}}\frac{\partial X_{output1}}{\partial w_{1,1}} \]

更清晰一點(指矩陣形式轉成求和形式),對應函式為:

\[L(W) = (Output_1 - Y_1)^2 + (Output_2 - Y_2)^2 \]

\[Output_1 = Sigmoid(X_{output1}) \]

這裡\(w_{1,1}\)指第一個隱藏層輸出對應第一個輸出的權值,實際上權值矩陣是轉置後的

\[X_{output1} = w_{1,1} * Output_{hidden1} + w_{2,1} * Output_{hidden2} + w_{3,1} * Output_{hidden3} \]

計算偏導

\[\frac{\partial L(W)}{\partial Output_{1}} = 2(Output_1 - Y_1) = 1.432 \]

\[\frac{\partial Output_{1}}{\partial X_{output1}} = Output_1(1 - Output_1) = 0.1989 \]

\[\frac{\partial X_{output1}}{\partial w_{1,1}} = Output_{hidden1} = 0.761 \]

最後得出答案

\[\frac{\partial L(W)}{\partial w_{1,1}} = 1.432 \times 0.1989 \times 0.761 = 0.217 \]

更新一下權重

\[\eta = 0.5 \\ w_{1,1} = w_{1,1} - \eta \frac{\partial L(W)}{\partial w_{1,1}} = 0.3 - 0.5 \times 0.217 = 0.1915 \]

大功告成!是不是很簡單(我知道不簡單),不過好在你只要知道怎麼算就行了,具體調包就完事了。

5. 調包實現 BP

包介紹

在 R 語言中可以用 nnet 和 neuralnet 包實現,前者引數簡單,後者引數複雜。

看看就好,看例子就會了

單層的前向神經網路模型在包 nnet 中的 nnet 函式,其呼叫格式為:

nnet(formula, data, weights, size, Wts, linout = F, entropy = F, softmax = F, skip = F, rang = 0.7,decay = 0, maxit = 100, trace = T)

擬合

predict(model, data)

引數說明:
size, 隱層結點數;

decay(就是學習率), 表明權值是遞減的(可以防止過擬合);

linout, 線性輸出單元開關;

skip,是否允許跳過隱層;

maxit, 最大迭代次數;

Hess, 是否輸出 Hessian 值(似乎是用來判斷函式凹凸性的,Hessian(半)正定則為凸函式)

可多層的 neuralnet 包

neuralnet(formula, data, hidden = 1, threshold = 0.01,stepmax = 1e+05, rep = 1, startweights = NULL,learningrate.limit = NULL,learningrate.factor = list(minus = 0.5, plus = 1.2),learningrate=NULL, lifesign = "none",lifesign.step = 1000, algorithm = "rprop+",err.fct = "sse", act.fct = "logistic",linear.output = TRUE, exclude = NULL,constant.weights = NULL, likelihood = FALSE)

擬合

compute(model, data(without output column))

引數說明:
formula :公式

data :建模的資料

hidden :每個隱藏層的單元個數(可以用向量新增隱層數目,如 c(6,4))

threshold :誤差函式的停止閾值

stepmax :最大迭代次數

rep :神經網路訓練的重複次數

startweights :初始權值,不會隨機初始權值

learningrate.limit :學習率的上下限,只針對學習函式為 RPROP 和 GRPROP

learningrate.factor :同上,不過可以是針對多個

learningrate :演算法的學習速率,只針對 BP 演算法

lifesign :神經網路計算過程中列印多少函式{none、minimal、full}

lifesign.stepalgorithm :計算神經網路的演算法{ backprop , rprop+ , rprop- , sag , slr }

err.fct :計算誤差,’{sse,se}

act.fct :啟用函式,{logistic,tanh}

linear.output :是否線性輸出,即是迴歸還是分類

exclude :一個用來指定在計算中將要排除的權重的向量或矩陣,如果給的是一個向量,則權重的位置很明確,如果是一個 n*3 的矩陣,則會排除 n 個權重,第一列表示層數,第二列,第三列分別表示權重的輸入單元和輸出單元

constant.weights :指定訓練過程中不需要訓練的權重,在訓練中看做固定值

likelihood :邏輯值,如果損失函式使負對數似然函式,那麼資訊標準 AIC 和 BIC 將會被計算

例項

資料:混凝土強度資料集

Pre

concrete <- read.csv("concrete.csv")
str(concrete)

# 沒有證據表明歸一化會有效,但確實有點效
normalize <- function(x) {
  return((x - min(x)) / (max(x) - min(x)))
}

# apply normalization to entire data frame
concrete_norm <- as.data.frame(sapply(concrete, normalize))

# confirm that the range is now between zero and one
summary(concrete_norm$strength)

# compared to the original minimum and maximum
summary(concrete$strength)

# create training and test data
set.seed(20021119)
index <- sample(x=c(1,2),nrow(concrete_norm),replace=T, prob=c(0.75,0.25))
concrete_train <- concrete_norm[index==1, ] #75%
concrete_test <- concrete_norm[index==2, ] #25%
summary(concrete_train$strength)

nnet

library(nnet)
nnet <- nnet(concrete_train$strength~., data=concrete_train,
             size = 10,decay = 5e-4, maxit = 200)
summary(nnet)
pred = predict(nnet, concrete_test)
cor(pred, concrete_test$strength)
plot(pred, concrete_test$strength)

neuralnet

無引數

library(neuralnet)

# simple ANN with only a single hidden neuron
set.seed(20021119) # to guarantee repeatable results
concrete_model <- neuralnet(strength ~ cement + slag +
                              ash + water + superplastic +
                              coarseagg + fineagg + age,
                            data = concrete_train)

# visualize the network topology
plot(concrete_model)

## Step 4: Evaluating model performance ----

# obtain model results
model_results <- compute(concrete_model, concrete_test[1:8])

# obtain predicted strength values
predicted_strength <- model_results$net.result

# examine the correlation between predicted and actual values
cor(predicted_strength, concrete_test$strength)
plot(predicted_strength, concrete_test$strength)

調引數

concrete_model2 <- neuralnet(strength ~ cement + slag +
                               ash + water + superplastic +
                               coarseagg + fineagg + age,
                             data = concrete_train, hidden =c(10,5))
# 透過hidden的向量化控制隱層數目
# plot the network
plot(concrete_model3)

# evaluate the results as we did before
model_results3 <- compute(concrete_model3, concrete_test[1:8])
predicted_strength3 <- model_results3$net.result
cor(predicted_strength3, concrete_test$strength)
plot(predicted_strength3, concrete_test$strength)

模型網路結構
20230506232814

相關文章