閱讀732 返回首頁    go 阿裏雲 go 技術社區[雲棲]


深度學習應用:入門篇(下)

四、經典入門demo:識別手寫數字(MNIST)

常規的編程入門有“Hello world”程序,而深度學習的入門程序則是MNIST,一個識別28*28像素的圖片中的手寫數字的程序。

MNIST的數據和官網:
https://yann.lecun.com/exdb/mnist/

深度學習的內容,其背後會涉及比較多的數學原理,作為一個初學者,受限於我個人的數學和技術水平,也許並不足以準確講述相關的數學原理,因此,本文會更多的關注“應用層麵”,不對背後的數學原理進行展開,感謝諒解。

  1. 加載數據
    程序執行的第一步當然是加載數據,根據我們之前獲得的數據集主要包括兩部分:60000的訓練數據集(mnist.train)和10000的測試數據集(mnist.test)。裏麵每一行,是一個2828=784的數組,數組的本質就是將2828像素的圖片,轉化成對應的像素點陣。
    例如手寫字1的圖片轉換出來的對應矩陣表示如下:

    之前我們經常聽說,圖片方麵的深度學習需要大量的計算能力,甚至需要采用昂貴、專業的GPU(Nvidia的GPU),從上述轉化的案例我們就已經可以獲得一些答案了。一張784像素的圖片,對學習模型來說,就有784個特征,而我們實際的相片和圖片動輒幾十萬、百萬級別,則對應的基礎特征數也是這個數量級,基於這樣數量級的數組進行大規模運算,沒有強大的計算能力支持,確實寸步難行。當然,這個入門的MNIST的demo還是可以比較快速的跑完。
    Demo中的關鍵代碼(讀取並且加載數據到數組對象中,方便後麵使用):

  2. 構建模型
    MNIST的每一張圖片都表示一個數字,從0到9。而模型最終期望獲得的是:給定一張圖片,獲得代表每個數字的概率。比如說,模型可能推測一張數字9的圖片代表數字9的概率是80%但是判斷它是8的概率是5%(因為8和9都有上半部分的小圓),然後給予它代表其他數字的概率更小的值。

    MNIST的入門例子,采用的是softmax回歸(softmax regression),softmax模型可以用來給不同的對象分配概率。
    為了得到一張給定圖片屬於某個特定數字類的證據(evidence),我們對圖片的784個特征(點陣裏的各個像素值)進行加權求和。如果某個特征(像素值)具有很強的證據說明這張圖片不屬於該類,那麼相應的權重值為負數,相反如果某個特征(像素值)擁有有利的證據支持這張圖片屬於這個類,那麼權重值是正數。類似前麵提到的房價估算例子,對每一個像素點作出了一個權重分配。
    假設我們獲得一張圖片,需要計算它是8的概率,轉化成數學公式則如下:

    公式中的i代表需要預測的數字(8),代表預測數字為8的情況下,784個特征的不同權重值,代表8的偏置量(bias),X則是該圖片784個特征的值。通過上述計算,我們則可以獲得證明該圖片是8的證據(evidence)的總和,softmax函數可以把這些證據轉換成概率 y。(softmax的數學原理,辛苦各位查詢相關資料哈)
    將前麵的過程概括成一張圖(來自官方)則如下:

    不同的特征x和對應不同數字的權重進行相乘和求和,則獲得在各個數字的分布概率,取概率最大的值,則認為是我們的圖片預測結果。
    將上述過程寫成一個等式,則如下:

    該等式在矩陣乘法裏可以非常簡單地表示,則等價為:

    不展開裏麵的具體數值,則可以簡化為:

    如果我們對線性代數中矩陣相關內容有適當學習,其實,就會明白矩陣表達在一些問題上,更易於理解。如果對矩陣內容不太記得了,也沒有關係,後麵我會附加上線性代數的視頻。
    雖然前麵講述了這麼多,其實關鍵代碼就四行:

    上述代碼都是類似變量占位符,先設置好模型計算方式,在真實訓練流程中,需要批量讀取源數據,不斷給它們填充數據,模型計算才會真實跑起來。tf.zeros則表示,先給它們統一賦值為0占位。X數據是從數據文件中讀取的,而w、b是在訓練過程中不斷變化和更新的,y則是基於前麵的數據進行計算得到。

  3. 損失函數和優化設置
    為了訓練我們的模型,我們首先需要定義一個指標來衡量這個模型是好還是壞。這個指標稱為成本(cost)或損失(loss),然後盡量最小化這個指標。簡單的說,就是我們需要最小化loss的值,loss的值越小,則我們的模型越逼近標簽的真實結果。
    Demo中使用的損失函數是“交叉熵”(cross-entropy),它的公式如下:

    y 是我們預測的概率分布, y' 是實際的分布(我們輸入的),交叉熵是用來衡量我們的預測結果的不準確性。TensorFlow擁有一張描述各個計算單元的圖,也就是整個模型的計算流程,它可以自動地使用反向傳播算法(backpropagation algorithm),來確定我們的權重等變量是如何影響我們想要最小化的那個loss值的。然後,TensorFlow會用我們設定好的優化算法來不斷修改變量以降低loss值。
    其中,demo采用梯度下降算法(gradient descent algorithm)以0.01的學習速率最小化交叉熵。梯度下降算法是一個簡單的學習過程,TensorFlow隻需將每個變量一點點地往使loss值不斷降低的方向更新。
    對應的關鍵代碼如下:

    備注內容:
    交叉熵:https://colah.github.io/posts/2015-09-Visual-Information/
    反向傳播:https://colah.github.io/posts/2015-08-Backprop/

在代碼中會看見one-hot vector的概念和變量名,其實這個是個非常簡單的東西,就是設置一個10個元素的數組,其中隻有一個是1,其他都是0,以此表示數字的標簽結果。
例如表示數字3的標簽值:
[0,0,0,1,0,0,0,0,0,0]

  1. 訓練運算和模型準確度測試
    通過前麵的實現,我們已經設置好了整個模型的計算“流程圖”,它們都成為TensorFlow框架的一部分。於是,我們就可以啟動我們的訓練程序,下麵的代碼的含義是,循環訓練我們的模型500次,每次批量取50個訓練樣本。

    其訓練過程,其實就是TensorFlow框架的啟動訓練過程,在這個過程中,python批量地將數據交給底層庫進行處理。
    我在官方的demo裏追加了兩行代碼,每隔50次則額外計算一次當前模型的識別準確率。它並非必要的代碼,僅僅用於方便觀察整個模型的識別準確率逐步變化的過程。

    當然,裏麵涉及的accuracy(預測準確率)等變量,需要在前麵的地方定義占位:

    當我們訓練完畢,則到了驗證我們的模型準確率的時候,和前麵相同:

    我的demo跑出來的結果如下(softmax回歸的例子運行速度還是比較快的),當前的準確率是0.9252:

  2. 實時查看參數的數值的方法
    剛開始跑官方的demo的時候,我們總想將相關變量的值打印出來看看,是怎樣一種格式和狀態。從demo的代碼中,我們可以看見很多的Tensor變量對象,而實際上這些變量對象都是無法直接輸出查看,粗略地理解,有些隻是占位符,直接輸出的話,會獲得類似如下的一個對象:
    Tensor("Equal:0", shape=(?,), dtype=bool)
    既然它是占位符,那麼我們就必須喂一些數據給它,它才能將真實內容展示出來。因此,正確的方法是,在打印時通常需要加上當前的輸入數據給它。
    例如,查看y的概率數據:
    print(sess.run(y, feed_dict={x: batchxs, y: batch_ys}))
    部分非占位符的變量還可以這樣輸出來:
    print(W.eval())

總的來說,92%的識別準確率是比較令人失望,因此,官方的MNIST其實也有多種模型的不同版本,其中比較適合圖片處理的CNN(卷積神經網絡)的版本,可以獲得99%以上的準確率,當然,它的執行耗時也是比較長的。
(備注:cnn_mnist.py就是卷積神經網絡版本的,後麵有附帶微雲網盤的下載url)
前饋神經網絡(feed-forward neural network)版本的MNIST,可達到97%:

分享在微雲上的數據和源碼:
https://url.cn/44aZOpP
(備注:國外網站下載都比較慢,我這份下載相對會快一些,在環境已經搭建完畢的情況下,執行裏麵的run.py即可)

五、和業務場景結合的demo:預測用戶是否是超級會員身份
根據前麵的內容,我們對上述基於softmax隻是三層(輸入、處理、輸出)的神經網絡模型已經比較熟悉,那麼,這個模型是否可以應用到我們具體的業務場景中,其中的難度大嗎?為了驗證這一點,我拿了一些現網的數據來做了這個試驗。

  1. 數據準備

    我將一個現網的電影票活動的用戶參與數據,包括點擊過哪些按鈕、手機平台、IP地址、參與時間等信息抓取了出來。其實這些數據當中是隱含了用戶的身份信息的,例如,某些禮包的必須是超級會員身份才能領取,如果這個按鈕用戶點擊領取成功,則可以證明該用戶的身份肯定是超級會員身份。當然,我隻是將這些不知道相不相關的數據特征直觀的整理出來,作為我們的樣本數據,然後對應的標簽為超級會員身份。
    用於訓練的樣本數據格式如下:

    第一列是QQ號碼,隻做認知標識的,第二列表示是否超級會員身份,作為訓練的標簽值,後麵的就是IP地址,平台標誌位以及參與活動的參與記錄(0是未成功參與,1表示成功參與)。則獲得一個擁有11個特征的數組(經過一些轉化和映射,將特別大的數變小):
    [0.9166666666666666, 0.4392156862745098, 0.984313725490196, 0.7411764705882353, 0.2196078431372549, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]
    對應的是否是超級數據格式如下,作為監督學習的標簽:
    超級會員:[0, 1]
    非超級會員:[1, 0]

這裏需要專門解釋下,在實際應用中需要做數據轉換的原因。一方麵,將這些數據做一個映射轉化,有助於簡化數據模型。另一方麵,是為了規避NaN的問題,當數值過大,在一些數學指數和除法的浮點數運算中,有可能得到一個無窮大的數值,或者其他溢出的情形,在Python裏會變為NaN類型,這個類型會破壞掉後續全部計算結果,導致計算異常。
例如下圖,就是特征數值過大,在訓練過程中,導致中間某些參數累計越來越大,最終導致產生NaN值,後續的計算結果全部被破壞掉:

而導致NaN的原因在複雜的數學計算裏,會產生無窮大或者無窮小。例如,在我們的這個demo中,產生NaN的原因,主要是因為softmax的計算導致。

RuntimeWarning: divide by zero encountered in log

剛開始做實際的業務應用,就發現經常跑出極奇怪異的結果(遇到NaN問題,我發現程序也能繼續走下去),幾經排查才發現是NAN值問題,是非常令人沮喪的。當然,經過仔細分析問題,發現也並非沒有排查的方式。因為,NaN值是個奇特的類型,可以采用下述編碼方式NaN != NaN來檢測自己的訓練過程中,是否出現的NaN。
關鍵程序代碼如下:

我采用上述方法,非常順利地找到自己的深度學習程序,在學習到哪一批數據時產生的NaN。因此,很多原始數據我們都會做一個除以某個值,讓數值變小的操作。例如官方的MNIST也是這樣做的,將256的像素顏色的數值統一除以255,讓它們都變成一個小於1的浮點數。
MNIST在處理原始圖片像素特征數據時,也對特征數據進行了變小處理:

NaN值問題一度深深地困擾著我(往事不堪回首-__-!!),特別放到這裏,避免入門的同學踩坑。

  1. 執行結果
    我準備的訓練集(6700)和測試集(1000)數據並不多,不過,超級會員身份的預測準確率最終可以達到87%。雖然,預測準確率是不高,這個可能和我的訓練集數據比較少有關係,不過,整個模型也沒有花費多少時間,從整理數據、編碼、訓練到最終跑出結果,隻用了2個晚上的時間。

    下圖是兩個實際的測試例子,例如,該模型預測第一個QQ用戶有82%的概率是非超級會員用戶,17.9%的概率為超級會員用戶(該預測是準確的)。

    通過上麵的這個例子,我們會發覺其實對於某些比較簡單的場景下應用,我們是可以比較容易就實現的。

六、其他模型

  1. CIFAR-10識別圖片分類的demo(官方)
    CIFAR-10數據集的分類是機器學習中一個公開的基準測試問題,它任務是對一組32x32RGB的圖像進行分類,這些圖像涵蓋了10個類別:飛機, 汽車, 鳥, 貓, 鹿, 狗, 青蛙, 馬, 船和卡車。
    這也是官方的重要demo之一。

    更詳細的介紹內容:
    https://www.cs.toronto.edu/~kriz/cifar.html
    https://tensorfly.cn/tfdoc/tutorials/deep_cnn.html

該例子執行的過程比較長,需要耐心等待。
我在機器上的執行過程和結果:
cifar10_train.py用於訓練:

cifar10_eval.py用於檢驗結果:

識別率不高是因為該官方模型的識別率本來就不高:

另外,官方的例子我首次在1月5日跑的時候,還是有一些小問題的,無法跑起來(最新的官方可能已經修正),建議可以直接使用我放到微雲上的版本(代碼裏麵的log和讀取文件的路徑,需要調整一下)。
源碼下載:https://url.cn/44mRzBh

微雲盤裏,不含訓練集和測試集的圖片數據,但是,程序如果檢測到這些圖片不存在,會自行下載:

  1. 是否大於5歲的測試demo
    為了檢驗softma回歸模型是否能夠學習到一些我自己設定好的規則,我做了一個小demo來測試。我通過隨機數生成的方式構造了一係列的數據,讓前麵的softmax回歸模型去學習,最終看看模型能否通過訓練集的學習,最終100%預測這個樣本數據是否大於5歲。
    模型和數據本身都比較簡單,構造的數據的方式:
    我隨機構造一個隻有2個特征緯度的樣本數據,[year, 1],其中year隨機取值0-10,數字1是放進去作為幹擾。
    如果year大於5歲,則標簽設置為:[0, 0, 1];
    否則,標簽設置為:[0, 1, 0]。

生成了6000條假訓練集去訓練該模型,最終它能做到100%成功預測準確:

微雲下載(源碼下載):
https://url.cn/44mKFNK

  1. 基於RNN的古詩學習
    最開頭的AI寫古詩,非常令人感到驚豔,那個demo是美國的一個研究者做出來的,能夠根據主題生成不能的古詩,而且古詩的質量還比較高。於是,我也嚐試在自己的機器上也跑一個能夠寫古詩的模型,後來我找到的是一個基於RNN的模型。RNN循環神經網絡(Recurrent Neural Networks),是非常常用的深度學習模型之一。我基於一個外部的demo,進行一些調整後跑起一個能夠學習古詩和寫古詩的比較簡單的程序。

執行寫詩(讓它寫了十首):
抑滴留居瀲罅斜,二川還羨五侯家。古劉稱士身相染,桃李栽林欲稱家。回首二毛相喘日,萬當仙性盡甘無。如何羽馬嘶來淚,不信紅峰一寸西。
廢寺鬆陰月似空,垂楊風起晚光催。烏心不把嫌香徑,出定滄洲幾好清。蘭逐白頭鄰斧蝶,蒼蒼歸路自清埃。漁樵若欲斜陽羨,桂苑西河碧朔來。
遙天花落甚巫山,鳳珮飛馳不騁莊。翠初才象飲毫勢,上月朱爐一重牛。香催戍渚同虛客,石勢填樓取蕊紅。佳句舊清箱畔意,剪顏相激菊花繁。
江上蕭條第一取,名長經起月還遊。數尺溫皋雲戰遠,放船鄉鬼蘸雲多。相逢檻上西風動,莫聽風煙認釣魚。堤費禽雛應昨夢,去朝從此滿玄塵。
避命拋醺背暮時,見川誰哭夢知年。卻隨筵裏腥消極,不遇嘉唐兩帶春。大歲秘魔窺石稅,鶴成應聽白雲中。朝浮到岸鴟巇恨,不向青青聽徑長。
楚田餘絕宇氤氳,細雨洲頭萬裏涼。百葉長看如不盡,水東春夜足殘峰。湖頭風浪斜暾鼓,北闕別罹初裏村。山在四天三顧客,轆轤爭養抵丹墀。
九日重門攜手時,吟疑須渴辭金香。釣來猶繞結茶酒,衣上敬亭寧強燒。自明不肯疑恩日,琴館寒霖急暮霜。劃口濡於孤姹末,出謝空卿寄銀機。蓮龕不足厭絲屨,華騎敷砧出釣磯。
為到席中逢舊木,容華道路不能休。時閑客後多時石,暗水天邊暖人說。風弄霜花嗥明鏡,犀成磨逐乍牽腸。何勞相聽真行侍,石石班場古政蹄。
聽巾邑外見朱蘭,雜時臨廂北滿香。門外玉壇花府古,香牌風出即升登。陵橋翠黛銷仙妙,曉接紅樓疊影聞。敢把苦謠金字表,應從科劍獨頻行。
昨日榮枯桃李慶,紫騮堅黠自何侵。險知河在皆降月,漢縣煙波白發來。仍省封身明月閣,不知吹水洽誰非。更擬慚送風痕去,隻怕鯨雛是後仙。

另外,我抽取其中一些個人認為寫得比較好的詩句(以前跑出來的,不在上圖中):

該模型比較簡單,寫詩的水平不如最前麵我介紹的美國研究者demo,但是,所采用的基本方法應該是類似的,隻是他做的更為複雜。
另外,這是一個通用模型,可以學習不同的內容(古詩、現代詩、宋詞或者英文詩等),就可以生成對應的結果。

七、深度學習的入門學習體會

  1. 人工智能和深度學習技術並不神秘,更像是一個新型的工具,通過喂數據給它,然後,它能發現這些數據背後的規律,並為我們所用。
  2. 數學基礎比較重要,這樣有助於理解模型背後的數學原理,不過,從純應用角度來說,並不一定需要完全掌握數學,也可以提前開始做一些嚐試和學習。
  3. 我深深地感到計算資源非常缺乏,每次調整程序的參數或訓練數據後,跑完一次訓練集經常要很多個小時,部分場景不跑多一些訓練集數據,看不出差別,例如寫詩的案例。個人感覺,這個是製約AI發展的重要問題,它直接讓程序的“調試”效率非常低下。
  4. 中文文檔比較少,英文文檔也不多,開源社區一直在快速更新,文檔的內容過時也比較快。因此,入門學習時遇到的問題會比較多,並且缺乏成型的文檔。

八、小結
我不知道人工智能的時代是否真的會來臨,也不知道它將要走向何方,但是,毫無疑問,它是一種全新的技術思維模式。更好的探索和學習這種新技術,然後在業務應用場景尋求結合點,最終達到幫助我們的業務獲得更好的成果,一直以來,就是我們工程師的核心宗旨。另一方麵,對發展有重大推動作用的新技術,通常會快速的發展並且走向普及,就如同我們的編程一樣,因此,人人都可以做深度學習應用,並非隻是一句噱頭。

參考文檔:
https://www.tensorfly.cn/
https://www.tensorflow.org/

最後更新:2017-04-20 09:00:36

  上一篇:go 功能太強了,容易出問題
  下一篇:go appcan案例書目錄