前言 — — 我們為什麼需要MLP
在我們的生活中,有很多的場景需要遇到分類的問題(最基本的「我要搞清楚現在的狀況」其實也可以視為一種分類)。決策很多時候和分類是完全綁定的,為此人們開發了許許多多的演算法來進行分類。在資料科學的領域內,我們也許會聽到很多名詞:人工智慧、機器學習、神經網路、深度學習。他們中的很多實例都是用來幫助我們決策(將輸入的情形分類)的。按照計算機界的共識,我們認為上述提到的名詞範圍中:人工智慧>機器學習>神經網路>深度學習。而我們在本文中要提到的MLP(多層感知機)就是有關神經網路和深度學習的。
不過我們為什麼會遇到這麼多概念呢?這可能就是資料量膨脹的壓力促使我們開發更加合適的演算法的結果。我們可以設想:如果要把一些簡單的情況分開,我們可以用類似if-else的判斷式,一次判斷個位數的資料特性(特徵)。但是當情況變得複雜,if-else判斷式的編寫就會變得十分複雜。當資料量龐大到一定程度而精確性要求沒有那麼高時(不要求100%正確),我們就可以用到機器學習。不過機器學習的「學習」通常需要資料科學家人工指定一些資料的特徵。給我們的計算機來達到更好的效果,所以也許在十幾個特徵、二十幾個特徵時,我們仍然有能力一一進行判斷(其實也很極限了),那麼到了更高的維度應該怎麼辦呢?
這就到了神經網路和深度學習的領域。以圖像識別為例,一張256*256解析度的點陣圖,每一個畫素都需要作為輸入訊息,也就成為了一個特徵。按照column順序匯入到計算機,就是256個特徵。人力篩選顯然不可能了,也許也沒有必要。這顯然就需要我們的計算機能夠自動與這些特徵「打交道」以執行我們的演算。以上就是一種粗淺的解釋,如果您看到了我們的《資料分析流程》一文,也許會對上述的內容有更多的共鳴。
我們在和計算機的互動中,應該認識到一個概念:計算機認知的是二進位,我們所有跟計算機打的交道都離不開數字和數學。如果要讓計算機幫助我們解決現實的問題,我們就需要用數學作為和計算機溝通的語言。我們有時對和計算機打交道時的數學語言感知不深,是因為許許多多的工程師把計算機語言封裝完畢,使我們的操作門檻降低了。其實說了這麼多,強調的就是一個數學語言。
數學語言有很大一部分,是函數和邏輯。我們用函數對事物進行建模,用邏輯操作他們之間的關係,就可以將我們看到的情形傳達給計算機。那麼計算機在特定的程式之下就可以按照我們的想法去演算並得到結果。我們很多的算法其實最終就是產出了一個函數的集合。這些函數可能是單一的,也可能是多個函數拼接而成。
機器學習、深度學習這類方法其實就是用來構建比較高深,但在特定領域非常有效的模型所用的方法。不過何謂簡單,何謂高深呢?我覺得一個比較簡單的例子就是:線型和非線性。就如我們看到一次函數會覺得它表示起來非常容易,但是看到二次函數就會覺得它比較複雜。我們在使用各種工具來表達我們想描述的情形時,就是在擬合函數以貼近實際的狀況。高級的工具配上複雜的資料,擬合出來的函數就會非常複雜,可以用在高維(維是線性代數的「維度」)空間中。他們的圖形不單單是電腦螢幕上就能完全繪製出來的,但是對於分類我們所告知的複雜情形,也許才會有用。
於是我們可以梳理一下思路:我們為什麼要分類 — — 我們需要看情況做決策(解決一些實際的問題);如何做分類 — — 使用模型;如何創造模型 — — 定義函數集;如何定義函數集 — — 使用各種演算法;如何選擇演算法 — — 以我們要討論的內容而言,我們會用機器學習和神經網路。而神經網路,就基於我們所要討論的基礎概念:多層感知機。理解了多層感知機之後,我們在接觸很多其他的神經網路結構時會更加容易理解他們的特點和原理。
人總是會遇到一些非常複雜的狀況。比如我之前遇到的一個有趣的情形:我看到一個有很多物品的房間裡,有個小孩在哭,然後他的媽媽認為他在假哭。在這裏,我們的大腦首先在一堆色彩、形狀各異的物品的房間中識別出了小孩,並且通過小孩的肢體語言、面部特徵以及其它特徵,判斷出小孩的面部表現是哭,但是實際上是在假哭。這個幾乎理所當然的判斷在我們看來似乎稀鬆平常,但是對於電腦來說可是非常複雜的。我們已經能夠從各種干擾中看到小孩,並且鎖定面部特徵,最終從眼淚是否存在這個重要特徵中看出小孩假哭。
但對於電腦,這個逐步發現問題所在的過程就比較複雜了。在神經網路的領域中,有一些人認為模仿人類最重要的生理結構 — — 神經系統是一種通向大量解決問題方法的關鍵(即便非常形式化,且到現在為止很多被提出的模仿只是淺嘗輒止)。如果我們要讓電腦模仿人腦做到類似的事情,從複雜的「仿生」角度來看,過程也許就是這樣的:人聰明(神經網路結構不錯)、資料好(提供一些高品質範例圖片和對應的答案,因為人也是通過學習例子來產生正確概念的)。經過了一些學習和訓練,我們通過近似於「仿生」設計的神經網路就可能會產生一個很棒的判斷機制。不過這其中的細節是難以描述的,或者說是建立在海量的細節之下。此處可以給出一篇參考文章。
然而生物的神經網路畢竟是十分特殊的結構。雖然我們可以看到很多名字號稱「BIONIC」的3C產品,但是真正的神經網路即便是訊號的傳遞(模擬訊號和數位訊號)也會和計算機有比較大的差異,所以說我們很多時候只是在一些模式上模仿了生物的神經網路特性。或者說生物的神經網路特性對於我們的資料處理和神經網路結構的設計有所啟發,比如貓的視覺研究之於CNN。詳情可以參考文章:
1. 概念
多層感知機(Multilayer Perceptron,縮寫MLP)也可以叫做人工神經網路(ANN),是最基本的神經網路結構。它的要素其實就是兩個:人工神經元和人工神經元之間的連接。ANN是一種前向結構(因為在簡單的模型表達中,我們希望處理資料的時候資料和結果的傳遞能做到單向)的人工神經網路,映射一組輸入向量到一組輸出向量。MLP可以被看作是一個有向圖,由多個的節點層所組成,每一層都全連接(可以用排列組合來理解這個拓撲結構)到下一層。除了輸入節點,每個節點都是一個帶有非線性激勵函數(用非線性函數才能讓我們擬合出非線性函數用來分類)的神經元(或稱處理單元)。一種被稱為反向傳播演算法的監督學習方法常被用來訓練MLP。MLP遵循人類神經系統原理,學習並進行資料預測。它首先學習,然後使用權重存儲資料,並使用演算法來調整權重並減少訓練過程中的偏差,即實際值和預測值之間的誤差。MLP的主要優勢在於其快速解決復雜問題的能力。多層感知的基本結構由三層組成:第一輸入層,中間隱藏層和最後輸出層,輸入元素和權重的乘積被饋給具有神經元偏差的求和結點,主要優勢在於其快速解決復雜問題的能力。MLP是感知機的推廣,解決了感知機不能對線性不可分資料進行分割的弱點。
1.1 MLP人工神經元的結構
上圖其實就是我們從神經元中抽象出來的數學模型。Inputs和Weights對應樹突,sigma的圓對應細胞核,Sum和Threshold所在的位置是軸突。Threshold所在位置在MLP中有一個名稱,叫做激勵函數。我們可以這麼看:有的神經訊號在傳遞過程中會消失掉。激勵函數其實就是在模仿這一過程。Output對應的就是軸突的末梢,將訊號傳遞出去。許許多多的人工神經元用定義好的結構和順序連結起來,就成了人工神經網路。
下圖引用自asimov institute,其中展示了很多種不同設計的人工神經元和人工神經網路結構。他們的功能和特性針對了不同的任務進行了特別的設計,需要另做研究。
1.2一個低維度的例子
1.2.1 完整的MLP模擬圖(2維輸入,4類輸出)
當我們假設有四個資料點(1,1)(-1,1)(-1,-1)(1,-1)分別屬於四個象限。此時我們給出一個新的資料點(2,2),想要讓機器知道它屬於哪個象限就很有意思。這個任務雖然可以用if-else分支來簡單完成,但是我們也可以用神經網路來完成這個任務,因為它可以被設計成神經網路擅長的分類任務。
1.2.2 數值在神經網路中的傳遞和權重(W)、偏置(b)
我們在圖中構建的神經網路,理論上已經可以擬合任何函數。我們可以先忽略ReLU,Softmax,Cross Entropy Error,就可以專注於其中的這些圓圈和上面的文字。
這些圓圈就可以認為是人工神經元。
讓我們先看到Input Layer:
在我們的例子中,Input Layer是座標值,例如(1,1),這是一個包含兩個元素的陣列,也可以看作是一個1*2的矩陣。輸入層的元素維度與輸入量的特徵息息相關,如果輸入的是一張32*32圖元的灰階圖像,那麼輸入層的維度就是32*32。這個32*32的維度也需要被展開到一維的array以輸入神經網路。
再讓我們看到Input Layer和Hidden Layer之間:
Input Layer和Hidden Layer的連接中有兩個重要參數:W1和b1。W代表weights,權重。b代表biases,偏置。他們在訓練神經網路時十分重要,攸關cost function的結果,考慮到之後的內容其實並不會對W和b進行客製化,所以在本文中,我們暫且如此描述以呈現一個規範的形式,深入一些的原理將會另有文章進行介紹。由X計算得到H十分簡單,就是矩陣運算:
H=X*W1+b1
線性代數中,當Hidden Layer設定為50-dimension(50維)之後,就會有大小為1*50的矩陣。
Hidden Layer是由W2和b2連接在Output Layer上的。同樣有矩陣運算。
Y=H*W2+b2
通過上述兩個線性方程的計算,我們就能得到最終的輸出Y了。
此時,我們不妨也提一下神經網路的「訓練」:
我們在學習中,會有講解給我們的知識和小練習,目的就是考察我們學習是否存在「偏差」。神經網路的訓練其實也大致如此:我們把訓練用的樣本輸入神經網路,將結果Y和樣本的真實結果進行計算,一種常見的計算方式就是紀錄樣本輸入之後每一項神經網路輸出的結果和每一項期望的輸出結果的差的平方和,再把所有樣本的這個平方和加總取平均,然後就得到了訓練網路的代價。那麼W和b就在其中發揮作用,同時也會隨著每一輪訓練收到反饋並且更改,最終讓神經網路有更好的表現。
說到這裡,如果你還記得前文提到的,「神經網路也是一種用來擬合複雜函數以解決問題的工具」,那麼你是否對此時線性代數的計算有疑問?如果你對線性代數的計算有印象的話,可知:一系列線性方程的運算最終都可以用一個線性方程表示。也就是說,上述兩個式子聯立後可以用一個線性方程表達。對於兩次神經網路是這樣,就算網路深度加到100層,也依然是這樣。這樣的話神經網路就失去了意義。
所以這裡要對網路注入精神:使用激勵函數為網路帶來非線性的表達能力。
1.2.3 激勵函數
簡而言之,啟動層是為矩陣運算的結果添加非線性的。常用的激勵函數有三種,分別是階躍函數、Sigmoid和ReLU。更多的激勵函數可以參考下文:
階躍函數(step function):階躍函數是有限段分段常數函數的組合。以分段方式引入非線性關係。
Sigmoid:當輸入趨近於正無窮/負無窮時,輸出無限接近於1/0。
ReLU:標準的ReLU函數表現為:當輸入小於0時,輸出0;當輸入大於0時,輸出等於輸入。
其中,階躍函數輸出值是跳變的,且只有二值,較少使用;Sigmoid函數在當x的絕對值較大時,曲線的斜率變化很小(梯度消失),並且計算較複雜;ReLU是當前較為常用的激勵函數。
激勵函數具體是怎麼計算的呢?
假如經過公式H=X*W1+b1計算得到的H值為:(1,-2,3,-4,7…),那麼經過階躍函數的激勵後就會 變為(1,0,1,0,1…),經過ReLU啟動層之後會變為(1,0,3,0,7…)。
需要注意的是,每個隱藏層計算(矩陣線性運算)之後,都需要加一層Activate(激勵),要不然該層線性計算是沒有意義的。
激勵函數在這裡的功能其實就是1.限制了輸出的幅度2.決定了哪些結果將被輸出、那些結果將不被輸出。
此時的神經網路變成了如下圖所示的形式:
神經網路的應用是分為“訓練”和“使用”兩個步驟的。如果是在“使用”的步驟,現在就已經完成整個過程了,在求得的Y(大小為1*4)矩陣中,數值最大的就代表著當前分類。
但是對於用於“訓練”的網路,還遠遠不夠。當前的輸出Y,還不夠理想。
1.2.4 輸出、評估和調整
現在,輸出Y的值可能會是(3,1,0.1,0.5)這樣的矩陣,誠然我們可以找到裡邊的最大值“3”,從而找到對應的分類為I,但是這並不直觀。如果最終的輸出為概率,也就是生成像(90%,5%,2%,3%)這樣的結果,這樣做不僅可以找到最大概率的分類,而且可以知道各個分類計算的概率值。
具體是怎麼計算的呢?
計算公式如下:
簡單來說分三步進行:(1)以e為底對所有元素求指數冪;(2)將所有指數冪求和;(3)分別將這些指數冪與該和做商。
這樣求出的結果中,所有元素的和一定為1,而每個元素可以代表概率值。
我們將使用這個計算公式做輸出結果正規化處理的層叫做Softmax層。
通過Softmax層之後,我們得到了I,II,III和IV這四個類別分別對應的概率,但是要注意,這是神經網路計算得到的概率值結果,而非真實的情況。
比如,Softmax輸出的結果是(90%,5%,3%,2%),真實的結果是(100%,0,0,0),因為我們的分類任務必須要把結果併入一類。雖然輸出的結果可以正確分類,但是與真實結果之間是有差距的,一個優秀的網路對結果的預測要無限接近於100%,為此,我們需要將Softmax輸出結果的好壞程度做一個“量化”。
一種直觀的解決方法,是用1減去Softmax輸出的概率,比如1–90%=0.1。不過更為常用且巧妙的方法是,求對數的負數。
還是用90%舉例,對數的負數就是:-log0.9=0.046
可以想見,概率越接近100%,該計算結果值越接近於0,說明結果越準確,該輸出叫做“交叉熵損失(Cross Entropy Error)”。
我們訓練神經網路的目的,就是盡可能地減少這個“交叉熵損失”。
上方即為神經網路的正向傳播過程。複習一下:神經網路的傳播都是形如Y=WX+b的矩陣運算;為了給矩陣運算加入非線性,需要在隱藏層中加入啟動層;輸出層結果需要經過Softmax層處理為概率值,並通過交叉熵損失來量化當前網路的優劣。
算出交叉熵損失後,就要開始倒傳遞了。倒傳遞(反向傳播)就是一個參數優化的過程,優化物件就是網路中的所有W和b(因為其他所有參數都是確定的)。
神經網路的神奇之處,就在於它可以自動做W和b的優化,在深度學習中,參數的數量有時會上億,不過其優化的原理和我們這個兩層神經網路是一樣的。
接下來的複雜之處即為:
1.參數優化會涉及到求導和梯度下降。
2.神經網路需要反覆運算迭代。
如上述例子中,第一次計算得到的機率是90%,交叉熵損失值是0.046;將該損失值反向傳播,使W1,b1,W2,b2做相應微調;再做第二次運算,此時的概率可能就會提高到92%,相應地,損失值也會下降,然後再反向傳播損失值,微調參數W1,b1,W2,b2。依次類推,損失值越來越小,直到我們滿意為止。
此時我們就得到了理想的W1,b1,W2,b2。
此時如果將任意一組座標作為輸入,利用完整的圖像的流程,就能得到分類結果。
2. 一個基於更加複雜的情景的程式範例(以MNIST為例)
上述的MLP範例用低維度的輸入和輸出簡單介紹了一下MLP。接下來,我們會使用Tensorflow和它內建的MNIST資料集來演示如何用現有的神經網路框架來構建簡單的模型,並將之前提到的概念對應到程式碼。
2.1環境設定
2.1.1 構建M1系列Mac上的Tensorflow環境
# <https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-MacOSX-arm64.sh>
# 從上面的地址下載miniforge,然後執行以下的指令chmod +x ~/Downloads/Miniforge3-MacOSX-arm64.sh
sh ~/Downloads/Miniforge3-MacOSX-arm64.sh
source ~/miniforge3/bin/activate# 然後創建虛擬環境
conda create -n tf2 python==3.9# 激活虛擬環境
conda activate tf2# 安裝我們需要的包# openblas在macOS 12.1中是需要先安裝的,不然我們安裝指定版本的numpy很容易失敗
pip install openblas
conda install -c apple tensorflow-deps
python -m pip install tensorflow-macos
python -m pip install tensorflow-metal
python -m pip install tensorflow-datasets
conda install jupyter pandas numpy matplotlib scikit-learn# 之後就可以執行jupyter notebook來進行後續的操作
jupyter notebook
2.1.2 構建Intel系列Mac上的Tensorflow環境
# <https://www.anaconda.com/downloads#macos>
# 從上面的地址下載有圖形介面安裝器的anaconda,然後按照預設的方式安裝# 然後用anaconda prompt創建虛擬環境
conda create -n tf2 python==3.9# 同樣用anaconda prompt激活虛擬環境
conda activate tf2# 安裝我們需要的包
conda install jupyter pandas numpy matplotlib scikit-learn
conda install tensorflow# 之後就可以執行jupyter notebook來進行後續的操作
jupyter notebook
2.2 MNIST code
我們要使用的資料集是手寫數字資料集:每個手寫數字的解析度是28*28,具體如圖所示。我們構建這個神經網路的目的就是用它附帶的60000個數字作為訓練集訓練神經網路,然後用剩下的10000個數字來驗證它的辨識正確率。
import tensorflow as tf
import time# 使用mnist資料集
mnist = tf.keras.datasets.mnist# 定義訓練集,測試集
(x_train, y_train),(x_test, y_test) = mnist.load_data()# 對輸入進行正規化(我們的輸入圖片是灰階圖,所以灰階的顏色會落在[0,255]之間)
# 我們需要將[0,255]壓縮到[0,1]之間,所以才有下方的這個程式碼
x_train, x_test = x_train / 255.0, x_test / 255.0# 構建神經網路的層
# Sequential在這裏的意思可以類比為做千層蛋糕。
# 在此模式下,裏面的每一層都能夠自動識別前面一層的輸出
model = tf.keras.models.Sequential([
# 圖片將以2D array的形式輸入再展開。因為是28*28的解析度,所以輸入的shape是(28,28)。Flatten就是把多維度的內容「展開」到一維,以適應神經網路層的型態。
tf.keras.layers.Flatten(input_shape=(28, 28)),
# 這一步創建了一個「密集連接」的神經網路層。
# 層中的每個輸入節點都連接到一個輸出節點。它從前一層接收輸入,這就是它密集的原因。
# 128代表這一層輸出空間的維數。這個的意義在現階段而言不大。需要研究其意義需要更深入地研究神經網路。
# 激勵函數接受網絡中節點的輸入,然後生成需要傳遞給下一層的輸出。
# 如果沒有非線性激活函數,這將只是一個線性回歸模型。
# 這裡使用的激活函數類型是 RELU 或 Rectified Linear Unit,它是最常用的激勵函數之一。
tf.keras.layers.Dense(128, activation='relu'),
# dropout層是一種正規化技術。其的工作原理是隨機選擇神經元並且將其在訓練中忽略。
# 在這裡,下一訓練週期(epoch)我們有20%的機率,或者說是每五個輸入中有一個會被忽略。
tf.keras.layers.Dropout(0.2),
# 與上面的RELU層類似,該層使用Softmax激勵函數。
# Softmax激勵函數的輸出類似於分類機率分佈(categorical probability distribution)
# 因此它告訴了我們一個類別為真的概率。
# 我們的手寫數字是0到9,總共10類。於是這個層需要10個神經元,然後用Softmax賦予每類機率。
tf.keras.layers.Dense(10, activation='softmax')
])model.compile(
# 優化器
optimizer='adam',
# 損失函數
loss='sparse_categorical_crossentropy',
# 評估標準
metrics=['accuracy']
)# 計算運行時間之紀錄開始時間
start = time.time()
# 使用mnist資料集訓練模型
model.fit(x_train, y_train, epochs=5)
# 計算運行時間之紀錄結束時間
end = time.time()
# 驗證模型的正確率
model.evaluate(x_test, y_test)
# 顯示使用的時間
print(end - start)
執行結果:
保存模型:
使用模型預測:
輸出結果中每個數字之後跟隨的小數即為這張圖片代表的真正數字的機率預測。
以上就是一個從概念引入到實際操作的過程。詳細的概念將會有針對性地進行補充。
參考
1.3Blue1Brown的YouTube教學
2.吳恩達Andrew Ng的機器學習https://study.163.com/course/courseLearn.htm?courseId=1004570029
教育部補助大專院校STEM領域及女性研發人才培育計畫目標為建構一個「以智慧物聯技術與實務應用為基礎的教育環境和實作場域」,並規劃出符合此STEM教育領域的創新特色課程,以畢業前進入企業實習的方式,讓學生了解相關產業界所面對的問題,再輔以業界實作場域的教育訓練活動,共同帶領學生發展出動手做、判斷與解決問題的相關技能;本計畫也規劃讓學生以專題實作的組隊方式,跟業界協力領導學生對外參與智慧物聯技術的應用競賽,不僅可以累積學生實務開發的能力,更能激發其潛能來幫助企業解決所面臨的難題。
Data Science Meetup 台灣資料科學社群的使命是「為資料科學人士與企業創建經濟機會」。我們相信大數據蘊藏著巨量的信息和價值,如何處理好大數據並發掘其潛藏的商業價值,就要靠資料科學有效的應用。21世紀是資料科學決勝時代,我們社群將為大家提供與資料科學相關的最新技術和資訊實戰攻略,並透過全球業界人士和學者幫助相關職業規劃與挑戰,社群活動包含
- 台北實體版聚
- 線上版聚
- Mentorship Program
歡迎加入我們社團瞭解更多資訊:
https://www.facebook.com/groups/datasciencemeetup/