2017/11/18

神經網路拼圖



以前我們往往以為:許多經典知識、科技、問題與演算法等等,不少以教科書的形式靜靜地躺在那兒,即使我們十年後再來翻閱可能都還是那樣子(靜靜地躺在那兒)。因為它們離我們的日常生活有些距離,甚至我們不知道何時才會使用到它們?

然而這個現象隨著「機器學習」與「人工智能」等科技的典範轉移有所改變,也著實對各領域同時產生巨大轉變並且深深的滲透到每個人的日常生活當中。知識遊牧民族於是又開始顯得焦慮:巨量新知識的「突現」有如宇宙初始時之「大霹靂」一般。

知識暴漲與進步的速度有多麼猛烈呢?舉個例子:現在許多國小學生已經開始用Scratch寫程式進而創作屬於自己的電玩,而目前以類神經網路為基礎的程式設計也像用Scratch做拼圖般的簡單,相信國中生花約10分鐘左右的課程作業幾乎可以打趴二十年前研究生窩兩年實驗室的成果(使用昂貴的工作站)。可以預期:在目前的教育政策、資源分配與環境之下,「接觸新知識」與「運用新技能」的能力於城鄉差異將會越來越大,這反過來須要靠網路知識的滲透來弭平。



秒做多層感知器(Multilayer Perceptron
我們於前一篇文章「深度學習引發之深度偏頭痛」中討論到層疊感知器的原因,以MNIST手寫字元資料集為例,若要實作輸入資料寬度78428x28像素)與輸出資料寬度100~9辨識結果)之手寫字元辨識系統,我們須引入至少一層隱藏層(假設256神經元),如下圖所示。



可以想像不久的將來,這個題目若以Scratch拼圖工具來做應該不用一分鐘。如下圖所示,深層類神經網路的配置非常適合以Scratch這樣的工具來實現。其中,神經元數量、激勵函數的選擇(例如,sigmoidrelusoftmax等)、感知器層疊數目、優化演算法的選擇(例如,SGDmomentumadagradeadam等)與損失函數等等,操作者只要把積木拼起來並加以選擇調配即可,不用管背後正向仿射(affine倒傳遞(back propagation或是梯度最佳化搜尋gradient descent等運算。



的不誇張,這個題目以Keras為例,扣除掉函式庫引入的描述大約只要下面三行Python程式碼(必要的神經元、權重、偏權值、損失函式參數設定與倒傳遞算法推衍等都自動設置):

model = Sequential()
model.add(Dense(input_dim=784,units=256,kernel_initializer='normal',activation='relu'))
model.add(Dense(units=10,kernel_initializer='normal',activation='softmax'))
model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])



即使對熟悉神經網路的老手而言,先透過拼圖般的規劃再對所編譯出來的TensorFlowPython程式碼進行細部修改也是不錯的方式。好比當初Arduino在設計與藝術等不同領域竄出頭來並且遍地開花,好的點子可以非常輕易地落實,也降低了設計原型的成本。降低「深層神經網路系統」與「機器學習」操作門檻之後,反而可以讓更多不同領域的人彼此腦力激盪出更棒的火花。因此中長期來看整件事情是好的,應該不是AI會不會取代人類工作的問題。



MNIST資料集
資料集中包含了從09的手寫數字圖片,總共有60000張訓練圖片及10000張測試圖片。我們可以很偷懶地透過PythonKeras懶人包套件來先一嗅端倪。首先假設讀者已經在Python環境安裝好TensorFlowKeras套件,如下面指令。

pip install tensorflow
pip instal keras



我們不用自己拜訪MNIST資料集位置或做下載的動做,下面程式碼可以直接透過mnist.load_data函式載入MNIST資料集。其中,60000張訓練集(_train)及10000張測試集(_test)被分開來儲存,各別包括影像資料(例如訓練用影像x_train2D)與輸出標籤(手寫文字圖形的答案,例如訓練用輸出標籤y_train_label)。讀者可以自行用x_train2D.shapey_train_label.shapex_train2D[0]y_train_label[0]等來檢視其內容(0表第一筆資料)。之後,我們對寫一個show_images的函式來批次顯示1~25張圖。

from matplotlib import pyplot as plt
from keras.datasets import mnist

# import MNIST data set
(x_train2D, y_train_label), (x_test2D, y_test_label) = mnist.load_data()

def show_images(img,img_label,imglist):
    gif=plt.gcf()
    gif.set_size_inches(8,10)
    ai=1
    for i in imglist:
        ax=plt.subplot(5,5,ai)
        ai+=1
        ax.set_title('label:'+str(img_label[i]),fontsize=10)
        ax.set_xticks([])
        ax.set_yticks([])
        ax.imshow(img[i],cmap='binary')
    plt.show()

# load 10 images (form index 1 to 10)
show_images(x_train2D,y_train_label,tuple(range(1,11)))



下圖顯示MNIST資料集中第一筆到第十筆手寫字元的圖片與正確的標籤內容,其中每張圖都是28x28像素並以0~255的灰階值儲存。





輸入資料正規畫(Normalization
首先我們須引入需所要的Keras函式庫。由於原始的圖形資料格式是二維的28x28像素,因此須對輸入資料格式做轉換(變成一維)與正規畫處理,讓數值從原本0~255的分佈標準化為μ=0且σ=1的分佈,最後再將輸出的標籤轉換成one-hot編碼格式

# build multi-layer perceptron (MLP) with Keras
from keras.utils import np_utils as kutil
from keras.models import Sequential
from keras.layers import Dense

# convert to one-dimension
x_train = x_train2D.reshape(60000,784).astype('float32')
x_test = x_test2D.reshape(10000,784).astype('float32')

# normalization: mean=0, std=1
for i in range(len(x_train)):
    x=x_train[i]
    m=x.mean()
    s=x.std()
    x_train[i]=(x-m)/s
for i in range(len(x_test)):
    x=x_test[i]
    m=x.mean()
    s=x.std()
    x_test[i]=(x-m)/s

# convert label to on-hot encoding
y_train = kutil.to_categorical(y_train_label)
y_test = kutil.to_categorical(y_test_label)




秒做手寫字元辨識系統
真正用來做手寫字元辨識的層疊感知器系統只不過短短下面五行程式碼,其中Dense表示神經網路是使用全連結層(fully connected layer仿射層(affine-layer的活化函數(activation function)使用線性整流函數(ReLU,輸出層的活化函數使用softmax函數(使輸出值為0~1的機率分佈),而計算梯度的損失函數(loss function)則使用cross-entropy函數

# MLP handwritten character recognition
model = Sequential()
model.add(Dense(input_dim=784,units=256,kernel_initializer='normal',activation='relu'))
model.add(Dense(units=10,kernel_initializer='normal',activation='softmax'))
#model.summary()
model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])

# model fitting with training set
train = model.fit(x=x_train,y=y_train,validation_split=0.2,epochs=10,batch_size=100,verbose=2)


訓練時將樣本分割出另外20%做為交叉驗證(cross validation)以評估將來實際受測的可能準確率,每次取100筆資料做小批次採樣,並重複整個過程10次(epoch)。下面程式碼把每次遞回的辨識準確度依訓練樣本與驗證樣本各別做圖。

# display training history
plt.subplot(1,1,1)
plt.title('Train History')
plt.plot(train.history['acc'],'-o',label='train')
plt.plot(train.history['val_acc'],'-x',label='valid')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.grid()
plt.xticks(list(range(0,10)))
plt.legend()
plt.show()





因為做為交叉驗證的20%樣本並不列入訓練,因此即使模型對於訓練樣本的準確率可以高達99.6%,我們仍然可以看到模型對於不列入訓練的樣本準確率只有97.7%。其間的差距有可能是過度擬合所致,好比做一大堆考古題練習,一但真正考試的題目不曾出現在考古題庫中,考試就沒那麼理想。此時,可以試著加上Dropout層以縮小差距(例如每次訓練過程都隨機放棄掉50%的神經元),其它程式碼不變。

# MLP handwritten character recognition
from keras.layers import Dropout

model = Sequential()
model.add(Dense(input_dim=784,units=256,kernel_initializer='normal',activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(units=10,kernel_initializer='normal',activation='softmax'))
#model.summary()
model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])

# model fitting with training set
train = model.fit(x=x_train,y=y_train,validation_split=0.2,epochs=10,batch_size=200,verbose=2)



實驗結果顯示,加上Dropout層確實能讓訓練的模形更貼近現實(不過度樂觀)。「遺忘」更能貼近人類的天性,再加上若每次都能嘗試用不同的思考方式去做題庫練習,反而能固化所習得的技能,因而更能適應(應付)各種題型。



透過混淆矩陣(confusion matrix),我們可以進一步分析哪些類別最容易被混淆誤判,例如輸出標籤應為5的類別卻被判定為3的結果共有7張影像。

# confusion matrix analysis
import pandas as pd
predict = model.predict_classes(x_test)
cm=pd.crosstab(y_test_label,predict,rownames=['label'],colnames=['predict'])
print('Confusion Matrix:\n{}'.format(cm))





我們可以透過Pandas陣列的索引功能,將那些輸出標籤應為5的類別但被判定為3的圖片索引值列舉出來並批次顯示。

# shows those numbers labeled 5 but recognized as 3
df=pd.DataFrame({'label':y_test_label,'predict':predict})
ei=df.index[(df.label==5)&(df.predict==3)]  # list of error image index
show_images(x_test2D,y_test_label,tuple(ei))









參數優化
在前面層疊感知器的程式碼中有些參數可能需要做些微調以改善辨識成績,例如隱藏層的神經元數量、小批次訓練取樣大小與進行交叉驗證的百分比等等。因此考量未來實際可行的執行時間/效率,我們可能需要先採樣幾個大範圍的參數做Shmoo圖以找到較佳的參數設置甜蜜點。如下圖所示,當隱藏層只配置256個神經元時,小批次訓練取樣大小不宜太大(200是較佳的選擇)。同時可以觀察到,若隱藏層能配置512個神經元,辨識準確率還能進一步提高到98.3%






人類何苦為難機器呢?
不過,若再進一步細看一些圖檔(例如顯示所有標示為5的圖)會發現:哇塞!寫的像狗啃的,不要說類神經網路了,就筆者自己的腦神經網路都可能秀逗了,有些字是真的是5嗎?






深度學習有捷徑嗎?
有的,只要我們小心繞過地雷。坊間許多書籍,素質良莠不齊,太過艱深可能反而阻礙學習興趣。假若你跟筆者一樣沒甚時間看經典論文(應該說偷懶),下面這些書推薦給您參考:

Python初學特訓班,時間大約花2~3週,第一次接觸Python就能廣泛且快速地讓讀者喜歡上這個未來世界的必修工具/技能。Deep Learning(做者為日本人),必讀的書且非常淺顯易懂,大約花1~2週時間就可以讓我們從零開始(包括演算法介紹、倒傳遞理論推導與梯度優化的程式實作),用Python打造自己的深層神經網路。至於較高階的KerasTensorflow,筆者建議擺在前面介紹的兩本書之後看,期間也可以交叉瀏覽之前所遺忘(Dropout)的細節或原理,大約只需再花1~2週就可以玩出自己的花樣(就像玩樂高積木一樣)。



前前後後大概約只需「投資自己」兩個月時間,相信對自己將來找工作(interview)的籌碼會增加不少。然而,這些所學(投資)也都將是自己未來非常有用的隨身工具/技能,不會有任何損失。