2017/12/30

不發展 AI 會被淘汰?



常常看到許多財經雜誌寫著類似非常聳動的標題,但事實上倒也沒那麼誇張。若把AI的發展比喻成網際網路的基礎建設,並不是「網際網路」本身讓「網際網路」變得不可或缺,而是人與人之間的聯繫、互動與分享等需求所致。想想看,若我們提出來的應用若只是圖像辨識或語音辨識,那如何能讓AI變得那麼不可或缺?

現代人若沒有網路恐怕很難熬,但將來AI能有甚麼樣的應用會讓人沒有它不能活呢?



AI 的入行門檻遠比你想像的還低!
這波的AI大浪著實來的猛爆,多少人擁有十八般武藝總算有機會嶄露,有些人半路出家深怕跟不上浪頭或被大浪吞沒,當然也有不少人趁機打著AI的旗幟看看能不能在開始的混亂之中撈到一些珍珠貝殼(騙騙VC的計畫)。且讓我們靜觀整個局勢的發展,當海水退去之後,誰沒穿褲子將一目瞭然。



AI 重新定義程式開發的套路
深層神經網路猛爆式的發展,對程式設計師的思維模式來說也產生了實質的變遷:以往我們寫一個程式(無論習慣用C++JavaPython)不外乎就是變數宣告、記憶體配置、幾個內外迴圈再加幾個函式呼叫來達成某個特定任務;而到了神經網路則變成了框架拼圖、訓練/學習與正向的推論,當神經網路框架與網路參數都「固化」以後的系統即可執行與傳統程式架構有相同輸出/入功能的任務。




不過其中較困難的部份,包括倒傳遞理論解偏微分、度優化法Gradient Descent與網路參數最佳化等等都已經模組化與自動化。也就是說,「寫程式」這個工作有點變成了拼積木、訓練與看結果的過程(這個工作小朋友都會)。所以,程式設計師若不長進將會第一個被淘汰。

例如下面以Keras腳本所描述不超過15行的神經網路框架(CNN+MLP),讓我們輕鬆花個兩分鐘就能拼湊出一個CIFA10影像識別率超過75%準確率的神經網路。

model = Sequential()
model.add(Conv2D(32, (3, 3),input_shape=(32,32,3),padding='same',activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Conv2D(32, (3, 3),padding='same',activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2))
model.add(Conv2D(32, (3, 3),padding='same',activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))

model.add(Flatten())
model.add(Dense(units=1024,activation='relu'))
model.add(Dense(units=10,activation='softmax'))
model.add(Dropout(0.25))

model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])



跟以往處理很多變數、記憶體配置、內外迴圈加函式呼叫的程式思維模式完全不同,現在要做的是:睡覺前拼一版神經網路然後丟去雲端做訓練,隔天起來看結果(好結果,程式完成)。




比較有趣的是,我們還可以窺視神經網路到底是怎麼理解數據的呢?如下圖是樣本訓練完所自動產生的第一階CNN濾波器與原始影像執行卷積的結果:







有時候當神經網路把狗當成貓時(下面這些標示為「5狗」的圖被上述神經網路辨識為「3貓」了),內心還蠻欣慰的,人類其實還是蠻優秀的啦!當然,這可能是我的網路架構不好所致。





AI 硬體描述語言產生器(Hardware Description Language Generator
筆者大膽預測一下將來可能出現(或許多鴨子划水已經默默在孵化中)的開發工具。概念如下:透過類似Scratch界面的開發者平台(具有強大運算力的雲端伺服器),開發者根據應用需求與問題特性定義輸入維度、判定類別數與神經網路框架,經過幾次疊代調出最佳的網路參數之後就能自動產生該神經網路的源生Python程式碼,再經過語言模組的轉換可自動生成高階的硬體描述語言,之後便可交由傳統的IC生產製造流程。





自適應多任務強化學習(Adaptive Multi-Task Reinforcement Learning
要做一個AI很簡單,但要做一個實用且可適應不同任務進行動態訓練與學習的應用就要花一些腦筋。幸運的是,神經網路正向推論所需的硬體資源配置單純且大部分都可以共用,而相較複雜且須要耗費大量計算資源的部份應該交由強大的雲端伺服器來執行。這在目前以WiFi或未來5G的系統都非難事而且將順水推舟,因此可預期未來機器人的學習與適應力將越來越強,會產衍生出甚麼複雜的應用難以想像。






分散式神經元感知器(Distributed Perceptron
在即將普遍的5G萬物聯網時代,許多感測器(如城市攝影機、道路信號控制、高空遙測、橋樑監測或行車記錄器等)、普及的電腦、遊戲機、家電設備到個人的手持裝置與生物監測器等都可以分享出部份資源與計算力來當成感知器並組成巨大的神經網路。




就像本期「科學人」說的:「科學想像力沒有邊界」。而在這個新時代,AI的發展將不再受限於手邊能用的技術,而是取決於我們的想像力。與其擔心甚麼時候AI將取代人類的工作,或大言不慚的說要讓年輕人看到未來,可能更應該正視:小朋友其實比我們來的有創意多了!




2017/11/25

秒做 CNN 手寫數字辨識



降低AI開發的技術門檻能收到什麼好處呢?巨人們佈的局是建立新的生態體系(Ecosystem)與盤算將來的規模經濟綜效,而站在巨人肩膀上的你我究竟是看能得更遠?亦或只是最終實現巨人們野心過程被供養的代理人呢?於現在所謂「場景即商機」的時代,巨人們將更容易透過免費送我們玩的工具與我們樂此不疲的活動中洞悉未來更宏觀的趨勢呢!

這個問題還蠻樂觀的!未來跨技術領域的門檻將會越來越低(就像藝術工作者可以輕易的拿Arduino創作出比工科宅更棒的點子),相信跨領域的腦思維震盪定會迸出許多連巨人們都想像不到的火花(所以他們也非常積極地投資各種將來可能威脅到自己的新創)。倘若你滿腦子創新的火苗沒找到出口,跨領域進去半導體顛覆整個產業也蠻不錯的!

搞個BetaGo來做FloorplanP&RDeepCritic來評估製程/原件庫體質與正確的時序約束,DeepCoach來教新人一步一步做實體設計並優化功耗與效能,DeepRecipe來優化產品競爭力並提升產能等等。一點都不誇張,當你深入其中將發現:即使是自稱tier-one Fab所提供的設計方法與原件庫都有太多空間(甚至是bug)可以優化與改善。



深層卷積神經網路(Convolutional Neural Network
我們於前一篇文章「神經網路拼圖」中秒做了一下以MNIST資料集為基礎的手寫字元辨識系統,實作過程雖然如堆積木般簡單,然而實驗結果卻一點也不含糊(辨識率可達到98.3%)。這一次,我們試著再增加兩層卷積層(Convolutional Layer),再來秒做一下卷積神經網路(Convolutional Neural Network, CNN)看看效果如何?

若以Scratch來思考,概念如下圖:



相較前一版專案,所引用的函式庫與秀圖的程式碼沒變,只是增加了引用卷積層所需的函式庫(以藍色字體標示):

from matplotlib import pyplot as plt
from keras.datasets import mnist
from keras.utils import np_utils as kutil
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.layers import Conv2D, Flatten, MaxPooling2D

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()




輸入資料正規畫
相較前一版專案,由於前面使用的卷積層其輸入是對二維灰階影像(維度為28x28x1)做處理,因此所載入的資料集維度須做轉換,其它程式碼則不變。

# import MNIST data set
(x_train2D, y_train_label), (x_test2D, y_test_label) = mnist.load_data()
show_images(x_train2D,y_train_label,tuple(range(1,11)))

x_train = x_train2D.reshape(x_train2D.shape[0],28,28,1).astype('float32')
x_test = x_test2D.reshape(x_test2D.shape[0],28,28,1).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)




秒做手寫字元辨識系統
所增加的卷積層(Conv2D)包括池化層(Max Pooling)只有短短五行程式碼,其它如損失函數、網路參數的優化與小批次樣本訓練方式都不變。CNN第一階的16個濾波器系數大小為5X5,而第二階的16個濾波器系數大小為3X3,它們初始值(神經元的鍵值)都是隨機給定(訓練過程會逐漸萃取出適應所有訓練樣本的濾波器樣貌)。相較DenseConv2D神經元並不是全連結(fully connected,它是以執行影像卷積運算(convolution)的方式做部份連結並共享部份鍵值。

# CNN handwritten character recognition
model = Sequential()
model.add(Conv2D(
        input_shape=(28,28,1),
        filters=16,kernel_size= (5,5),
        padding='same',
        activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Conv2D(
        filters=16,kernel_size= (3,3),
        padding='same',
        activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(units=128,activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(units=10,activation='softmax'))

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)



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

# CNN handwritten character recognition
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()

# model evaluation with test data set
score = model.evaluate(x_test,y_test)





相較前一版專案,引入CNN之後的辨識率可以輕鬆地更進一步提高到99.03%以上了!



卷積層濾波器係數
我們曾經於「機器人(AI)創作」文中解釋過「卷積運算(Convolution)」的物理意義,例如經過高通濾波器的卷積運算會偵測出圖形的輪廓,而低通濾波器會使得輸出圖形變得平滑/模糊。只不過空間濾波器(例如短脈衝響應濾波器)係數是人決定,而CNN的濾波器係數值是透過訓練自動產生(神經網路依據訓練樣本自動調適出可萃取不同特徵的各式濾波器)。

麼?特徵自動萃取?這麼神奇的事值得我們細部研究!我們可以透過下面程式碼偷窺一下CNN所產生的濾波器(其實就隱身在CNN的神經元鍵值)長相:

# Given 16 filters after a specified layer, we can call plot_filters(layer,4,4) to get a 4x4 plot of all filters
def plot_filters(layer,x,y):
    print('filter of {} layer'.format(layer.name))
    filters = layer.get_weights()[0]
    (w,h,_,n)=filters.shape
    for j in range(n):
        ax=plt.subplot(y,x,j+1)
        ax.imshow(filters[:,:,0,j],cmap='binary')
        plt.xticks([])
        plt.yticks([])
    plt.show()
    return plt

plot_filters(model.layers[0],4,4)  # 1st convolution layer
plot_filters(model.layers[2],4,4)  # 2nd convolution layer



CNN萃取出的濾波器長相不難發現,它跟我們人類視覺處理過程有點類似:傾向萃取圖形輪廓的能力。下面是CNN第一階的165X5濾波器,可看出CNN想產生出能判斷這些手寫字元圖形中邊緣輪廓的能力。



我們可以將池化層(max pooling)的物理看成是降低空間解析度的過程(低通又允許保有一些銳利的細節)。而CNN第二階的16個濾波器,由於它的輸入是針對經過池化層(max pooling)的輸出(降維到14X14)做淬取,因此我們可以將其設計為3X3濾波器以節省資源配置。

下面是CNN第二階的濾波器係數(CNN的神經元鍵值),可看出CNN想產生出能判斷一些圖形中斜邊與直線等構造(紋理)的能力。





卷積層濾波器輸出
我們可以試著將濾波器輸出結果顯示出來,以證明前面所說:CNN想產生出能判斷文字圖形輪廓、斜邊或直線等構造的能力。由於CNN置於整個深層神經網路的前兩層,因此我們需要用到一些技巧來把先前訓練完成的網路截取出我們要的部份。此時,整個網路好比是一個model好的一個方程式。



以輸入測試樣本「7」為例,下面程式碼可以簡單的把第一階CNN的方程式截取出來。其中,module.layers[0]表示整個神經網路的第一階層(即CNN layer-1)。由於第一階CNN16個濾波器,其輸出也會有16張經過濾波器的輸出影像。

# CNN model extraction
from keras.models import Model

# output of CNN layer1
fun = Model(inputs=model.layers[0].input,outputs=model.layers[0].output) # extract CNN1 function
out = fun.predict(x_test[0:1])  # input the 1st mage: 7
(_,w,h,n) = out.shape  # (1, 28, 28, 16)
plt.imshow(out[0,:,:,0],cmap='binary') # show the 1st convolution output


根據所輸入的測試樣本,下面程式碼可以更進一步將16張經過濾波器(convolution)的輸出影像以陣列的形式批次顯示。注意,CNN第二階層的截取是從最頂層的輸入開始。

# Given 16 filters after a specified layer, we can get a 4x4 plot of all filters’ output
from keras.models import Model

def plot_cnn_output(dist_layer_id,test,x,y):
    li=model.layers[0]
    lo=model.layers[dist_layer_id]
    print('CNN output of layer {}'.format(lo.name))
    fun = Model(inputs=li.input,outputs=lo.output)  # extract layer function
    out = fun.predict(test)  # generate layet output
    (_,w,h,n) = out.shape
    for j in range(n):
        ax=plt.subplot(y,x,j+1)
        ax.imshow(out[0,:,:,j],cmap='binary')
        plt.xticks([])
        plt.yticks([])
    plt.show()
    return plt

plot_cnn_output(0,x_test[0:1],4,4) # CNN layer-1, given the 1st test image (input=7)
plot_cnn_output(2,x_test[0:1],4,4) # CNN layer-2, given the 1st test image (input=7)



CNN第一階的濾波器輸出(convolution)如預期,有如針對手寫字元圖形中的輪廓進行檢測。
  


CNN第二階的濾波器輸出結果,有如進一步將手寫字元圖形拆解成斜邊與直線等構造。





混淆矩陣分析(Confusion Matrix Analysis
同樣地,我們透過混淆矩陣進一步分析哪些類別最容易被混淆誤判,例如輸出標籤應為「5的類別卻被判定為「3的結果從上一回(DNN7張影像減少為4張(CNN)。

# 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))







 實驗結果顯示,CNN對數字「5」的辨識能力整體改善了許多。細部看那些剩下來標示為「5」卻被錯誤辨識為「3」的樣本確實不易判讀(有些模稜兩可)。





請注意!雖然CNN整體辨識率提高到99.03%,但對數字「7」的辨識能力反而出現了較高的混淆狀況(被辨識為「2」)。然而細部來看,大部分人類視覺還是區分的出來的!

我們若比喻上一回(DNN是針對輸入圖形中像素的絕對位置硬train,那麼CNN比較像是先將圖形分拆成數個紋理的構造再進行訓練。所以當數字「7」的寫法出現了類似「2」底部的橫向紋理時,CNN反而誤判了!真的是蠻有趣的現象。





這不是AI啊?
目前登陸火星仍然是很遙遠的夢想,但是先放風箏(光帆離子引擎)到火星甚至是太陽系外拍拍照或許是不錯的可行方法。