從鍋爐工到AI專家(4)

手寫數字識別問題

圖像識別是深度學習衆多主流應用之一,手寫數字識別則是圖像識別範疇簡化版的入門學習經典案例。在TensorFlow的官方文檔中,把手寫數字識別「MNIST」案例稱爲機器學習項目的「Hello World」。從這個案例開始,咱們的連載纔開始有了一些「人工智能」的感受。
問題的描述是這樣:
有一批手寫數字的圖片,對應數字0-9。經過機器學習的算法,將這些圖片對應到文本字符0-9。用通俗的話來講,就是計算機認出了圖片上面手寫的數字。
從問題描述可見這個機器學習項目的「Hello World」對於入門者來說,既很實用,也存在一些門檻。下面以咱們的節奏,儘量把這個問題分解,中間插入一些機器學習的基本概念,讓你們能夠輕鬆入門。html

線性迴歸和邏輯迴歸

也有人從線性迴歸和非線性迴歸的角度來說,由於邏輯迴歸就是非線性迴歸的一種。
不要被這些專有名詞嚇倒,其實重要的是你理解這個概念,以便之後碰到複雜問題的時候幫助你選擇更適合的算法。
線性迴歸是指數據集和結果都知足線性函數,也就是方程組是一次方的,不包含高次元。前面例子中的房價,雖然咱們例子中的參數不多,但即使參數大量增長,也基本符合線性迴歸機器學習的範疇。與此相似的還有基本股價預測、限定環境的金融分析等等。
邏輯迴歸主要使用數學sigmoid函數進行邏輯分類,在一般機器學習的概念中,這也是重要講解的部分,TensorFlow中已經預先封裝了對應的softmax函數,因此這裏咱們也忽略掉具體的算法實現,只要記住邏輯迴歸主要對應分類的學習方式就能夠了。好比本次MNIST的例子,就是把圖片分類到0-9這十個類別之一。與此相似的應用領域還有:垃圾郵件分類、產品質量自動檢測等。
(最下面參考連接中有相關公式的詳細介紹,有興趣的建議跳轉去閱讀。)python

監督學習和無監督學習

監督學習是指對於每個樣本,若是有給定的正確結果做爲學習依據,就屬於監督學習。例如MNIST手寫數字識別的每一副樣本圖片,咱們都有已知的標籤指明這是哪一個數字,這是監督學習。
無監督學習則相反,對於樣本咱們沒有指定的結果。例如咱們去看畫展,有幾幅畫看上去「很喜歡」,另外幾幅看起來則「不喜歡」。若是是由機器學習作這個判斷,則是典型的根據一些特徵進行了分類,但這個分類並非有準確答案指導的,只是把「某一類」的畫做放在一塊兒,這就屬於無監督學習。
瞭解這個概念的目的一樣是幫你分解問題及選取合適的算法。git

人工智能的基本工做模式

結合上面兩個概念,咱們已經能夠得出比較常見的人工智能的工做模式,這也是咱們第一篇文章中概念的延伸。程序員

  1. 學習階段:採集數據集->可能的認爲標註(監督學習)->機器學習系統->完成補全的機器學習系統(方程求解)
  2. 生產階段:生產數據集導入->完成的機器學習系統->分類後的結果(假設邏輯迴歸)

看起來結果很簡單,只是一個分類。但這個分類在咱們作數學模型設計的時候,可能包含了全部須要的可能。好比本例中的字符0-9。
拿人工智能典型應用再舉幾個例子:算法

  • 自動駕駛:是輸入各類攝像頭、傳感器檢測到的路面和周邊環境數據集,進行機器學習分類,最後獲得加油門、減油門、左轉、右轉、剎車、倒車燈動做。
  • 語音識別:MIC採集到的聲音樣本,輸出分類爲全部可能的字符或者字母,中間根據語氣可能插入幾個有限的標點符號。
  • 廣告推薦:根據採集到的我的偏好數據,推薦已經分類的有限內容。

固然人工智能的發展遠不只僅這些,機器學習的算法也在不斷的完善和發展過程當中。
整體上,只要能找到解決問題對應的算法,而後收集大量的數據集,並通過可能必要的標註,就可能把一個傳統用程序沒法解決的問題,轉化爲算法實現+數據處理的工程化問題,從而有了實現的可能。
反過來若是有人問:「機器學習能讓電腦打掃花園嗎?」,這一句話可能就帶出來很複雜的狀況,諸如「圖像識別」、「自動駕駛」還有一系列機械工程的問題,對於這樣複雜的問題,極可能是當前一個行業和領域單獨所沒法解決的。因此有的時候提及來簡單,仔細思考後會發現,反而不必定容易實現。編程

數據規範化

數據規範化(Normalization)也稱爲數據歸一化(Regularization),都是翻譯的詞彙,明白意思就好。前面第一個例子的時候已經講過一些,這裏須要再重點講一下。
使用TensorFlow等機器學習框架以後,原先最複雜的部分,好比算法實現,都已經由框架幫你解決掉了,徹底不懂算法也能夠靠抄樣例的方式完成工做。事實上如今新的算法不斷出現,你已經不太可能搞明白全部算法了,參考成熟算法完成工做已經成爲常態。
可是原來算法實現的工做量雖然下降了,但數據從採集、規範化、輸入給程序,到完成機器學習運算,再轉換爲合理的輸出。這些工做難度不只沒有下降,並且隨着機器學習應用場景的普遍化變得更爲複雜。
能夠說不少項目的阻礙,都在於沒法找到合理的數據規範化方法,從而沒法將機器學習應用到場景中。
以MNIST爲例,首先要作這幾樣事情,這也是一般圖像識別都要通過的步驟:小程序

  • 準備手寫數字的樣本圖片
  • 全部的樣本中,手寫數字的部分,在整個圖片中所佔的面積,基本相同
  • 手寫的數字在圖片中,應當都是正向的,不能有橫、有豎甚至還有傾斜的(數字自身手寫中應有的傾斜不算)
  • 全部樣本圖片,最終要使用徹底相同的分辨率,本例中統一使用28x28的點陣
  • 全部樣本圖片,要使用相同的圖形格式,好比手寫樣本用單色就好
  • 這一條就是咱們前面說過的,一般單色圖片沒字節數據是0-255的整數,要統一按比例轉換成0-1的浮點數

對於不一樣的系統,規範化可能作不一樣的工做,但大體原則是相似的。好比對於語音識別類的系統中:數組

  • 語音的採樣頻率必須是相同的
  • PCM聲音採樣整數數據,最後也要轉換成0-1的浮點數
  • ...

關於取值0-1範圍的浮點數的事情,也有一些例外的狀況。好比天然語言處理(NLP)系統中的「單詞向量化」問題,由於單詞或者詞語,長度是不一樣的,規範化起見,咱們只能把單詞轉換成數字,好比1表明the/2表明is,這種狀況下,這個數字是不能再規範化到0-1的浮點數的。緣由主要是這個整數通過運算後,結果必須仍然是精確的整數,不然即使差一點,單詞就徹底是另一個了。因此具體狀況仍是要具體分析。「單詞向量化」的問題屬於比較專業化的問題,我也不是專家,之後若是有機會咱們再分享。bash

數據預處理

由數據規範化帶來的數據預處理問題每每很複雜,幾乎每個機器學習系統中均可能有不一樣的實現。而機器學習自己的內核翻來覆去不過就那幾個算法,相似前兩年一個神經網絡就包打了天下。因此機器學習編程語言的能力,就有了很高的要求。
而python恰好是這樣可塑性極好的一種語言,而且有豐富的第三方擴展庫來實現各類各樣的功能。好比圖像處理的skimage / cv2(opencv) / pillow / matplotlib,聲音處理的librosa / eyed3 / pydub / pyaudio等。有句行間逗比的話說「沒有什麼是一個python庫解決不了的,若是有,那就是兩個」。
因此至少當前,機器學習的重點轉移到了數字化、大數據和算法,適應性如此之強的python就成了首先工具。
也由於數據預處理每每須要大量長期實際工做經驗的積澱,因此實際上機器學習行業雖然是新興學科,但仍然很須要大量經驗豐富的程序員加入其中。不少剛畢業的學生,由於瞭解到人工智能的火爆,在學校衝刺學習了機器學習的知識,但碰到具體問題時候每每沒法下手,所差的大多不是機器學習自己,而是在數據預處理方面經驗不足。網絡

樣本集分組

科學是能夠重複的,科學的研究須要科學的手段,很相似現代西醫對藥物臨牀試驗的要求,對於「魔法師」通常神奇的人工智能,驗證其有效性是很關鍵的環節。
對於收集到用於機器學習的樣本,一般是要劃分紅三組:訓練集、驗證集和測試集。分別用於對算法模型進行訓練、微調算法參數用驗證集樣本選擇最優的算法以及對一個訓練完成的模型使用測試集樣本計算這個模型的正確率。
徹底獨立的劃分紅三組數據的緣由比較複雜,這個緣由很相似藥物的臨時實驗的需求,在這裏也略去只說結果,一般會把數據集劃分紅60%:20%:20%的比例,固然具體狀況也要具體看。
本例中由於不涉及算法調優及算法優選的工做,因此通常只須要劃分紅訓練集和測試集兩部分就能夠。

MNIST的數據

很是幸運,畢竟TensorFlow官方文檔只是爲了介紹機器學習框架的工做,因此提供的數據樣本是已經完成預處理和規範化的。爲了便於你們之後解決相似的問題,在進入源代碼以前,咱們先把樣本數據展開作一個介紹,以便未來解決相似問題時候你們有一個參照。

如圖所示,這就是一副樣本圖片數字化以後的樣子:

  • 在灰度圖中,每一個字節表明圖中一個點,取值範圍本來爲0-255整數,這裏已經轉換成了0-1的浮點小數。
  • 圖片的分辨率是28x28點陣灰度圖,數字化以後是一個28x28的浮點數矩陣。在實際機器學習的計算中,這個圖片會進一步展開爲28x28=784的一維矩陣(向量)。
  • 灰度圖已經預先經過修圖、亮度對比度操做等,去掉了無心義的噪點干擾,好比看起來圖片中除了手寫以外的部分基本是0。(看到這裏,老讀者可能想起來本博中另外用OpenCV處理圖片的文章,如今你應當知道那些文章真正的目的是什麼了吧。)

這樣的圖片樣本,本例數據集中一共是60000幅,保存在mnist.train.images之中,排列起來以下所示:

做爲監督學習,每幅圖片咱們都有一我的爲標註,指明這幅圖片是哪一個數字。咱們剛纔講過了,這實際是一種分類算法,計算的結果並非直接獲得0-9數字,而是獲得一個分類信息,本例就是分紅10類。在這種表述方式中,數字n將表示成一個只有在第n維度(從0開始)數字爲1的10維向量。好比,標籤3將表示成([0,0,0,1,0,0,0,0,0,0,0])。所以, mnist.train.labels 是一個 [60000, 10] 的數字矩陣:

分類算法

分類在一般的機器學習中須要附加和屢次使用sigmoid公式(公式只能完成0、1兩種分類,屢次使用達成分多類),TensorFlow則內置了softmax函數(能夠完成多項分類)。
由於有這些內置的函數幫助,咱們已經不須要在具體算法上下太大的功夫,不過咱們這裏仍然作一個簡單的解釋,更詳細的說明其實這一部分的官方文檔算說的比較明白,能夠去參考。
回憶一下第一篇中的內容,機器學習最重要的假設就是,咱們認爲一切問題都是能夠用數學模型所描述的。引伸到MNIST案例中,由於咱們要分類10組,就能夠列出一個包含10項的方程式。咱們簡化一下,用一個只有3維的小方程來講明,方程式看起來相似是這樣子的:

這個方程式通過簡化、推導、矩陣化以後,就成爲了程序中所使用的語句:

y = tf.nn.softmax(tf.matmul(x,W) + b)

注意其中的x、W是矩陣,b則是向量。因此這一條語句,展開後實際上表明瞭10行、每行784個變量的方程式,若是沒有IT技術支持,這樣的方程式應當會哭死吧?
這也是較爲通用的一個分類算法,不少機器學習的系統中,這簡單的一行都是核心。
從直觀上看,分類算法的功能就是把很是多維的數據,本例中是784個,處理成少許的輸出,本例中是10個。事實上達成了降維的效果,因此也稱爲降維算法。
這很相似於人類利用自身的經驗、感知和判斷,經過獲取多種數據後,抽絲剝繭的思考,最終在有限的可能手段中作出選擇的過程。因此降維基本上是人工智能最經常使用的方法,同時也是機器學習算法「像」人的緣由。

看一眼咱們的數據

TensorFlow官方文檔幫咱們簡化了數據的規範化過程,這省了一大把力氣。另一方面,原本很接地氣的圖像識別,變得看不見摸不着,就算看到了MNIST的源碼和運行結果,不少人仍然感受不在掌握。歸根結底,就是那些數據,不是咱們熟悉了的格式。因此額外的,咱們加一個小程序,用於把MNIST的樣本數據,還原成肉眼可見的圖片文件,而且在下面進入MNIST源代碼講解以前,先熱身一下。

#!/usr/bin/env python
# -*- coding=UTF-8 -*-
#引入mnist數據預讀準備庫
import input_data
#tensorflow庫
import tensorflow as tf
#引入繪圖庫
import matplotlib.pyplot as plt

#這裏使用mnist數據預讀準備庫檢查給定路徑是已經有樣本數據,
#沒有的話去網上下載,並保存在指定目錄
#已經下載了數據的話,將數據讀入內存,保存到mnist對象中
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
#sess = tf.Session()
#使用交互模式初始化tf庫
sess = tf.InteractiveSession()

def toImage(image,filename):
    #剛纔咱們講過了,樣本數據是784長的向量數據,這裏重定義成28x28的圖片,每一個點1個數據
    x_image = tf.reshape(image, [28, 28, 1])

    #將規範化後0-1的浮點數,從新變成0-255的數據集
    x_image = 256 * x_image
    #取整
    x_image = tf.floor(x_image)
    #從浮點數轉換成無符號8位二進制數,也就是1個字節
    y_image = tf.image.convert_image_dtype(x_image, tf.uint8)
    #轉成jpeg圖像格式
    im = tf.image.encode_jpeg(y_image)

    #打開圖像文件並寫出
    f = open(filename, "wb+")
    #注意這裏就是tf運行的部分,交互模式下,可使用.eval的方式運行而不是一般的sess.run
    #f.write(im.eval(session = sess))
    f.write(im.eval())
    f.close()

#將樣本數據測試集的前三個樣本保存爲圖片    
babe = mnist.test.next_batch(1)
toImage(babe[0],"./digital1.jpeg")
babe = mnist.test.next_batch(1)
toImage(babe[0],"./digital2.jpeg")
babe = mnist.test.next_batch(1)
toImage(babe[0],"./digital3.jpeg")

這裏咱們展現了python跟外界數據集互動的方式,但願能夠加深你對機器學習的理解。這個例子中,新用戶在讀取數據集那一行屬於碰到問題最多,主要緣由是咱們在國內一個網絡高度不穩定的環境下。個人辦法是採用其它方式得到了數據文件,保存在指定目錄,省去啓動後再次下載數據。我這裏四個數據文件的列表以下:

-rw-r--r--  1 andrew  staff   1.6M Jan  6  2017 t10k-images-idx3-ubyte.gz
-rw-r--r--  1 andrew  staff   4.4K Jan  6  2017 t10k-labels-idx1-ubyte.gz
-rw-r--r--  1 andrew  staff   9.5M Jan  6  2017 train-images-idx3-ubyte.gz
-rw-r--r--  1 andrew  staff    28K Jan  6  2017 train-labels-idx1-ubyte.gz

下載數據的方法能夠參考input_data.py腳本,也能夠用上面給出的文件名在網上搜索,有國內的下載點。
程序中使用了互動模式初始化TensorFlow,也就是這一行:

sess = tf.InteractiveSession()

官方文檔中只是解釋這種互動式的初始化通常用在交互方式,同前一個例子用的tf.Session()相比,Session()初始化必須在數學模型所有構建完成以後,交互模式能夠一邊構建模型,一邊作一些運算好比插入一些圖。
實際上官方開發人員在諮詢問答中又給了更精確的一個解釋,它們惟一的區別在於:tf.InteractiveSession()把它自身做爲默認的session,tensor.eval()和operation.run()運行的時候,在後面不須要給出session的參數,直接使用默認session運行。而若是使用Session()初始化的話,上述兩類函數的執行,必須在後面顯示的給出使用哪個session執行該操做。上面源碼中Session初始化及最後寫出圖像數據的兩行,你能夠用註釋掉的內容自行測試一下就明白了。

程序生成的三幅圖片文件跟本篇第一幅圖片示例中左側的原圖樣式徹底一致,這裏就再也不貼圖了。這個例子的根本目的仍是讓你對tensorflow加深瞭解,而且更多的理解數據文件內容的前因後果。

初級mnist源碼

#!/usr/bin/env python
# -*- coding=UTF-8 -*-

import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

import tensorflow as tf

#定義佔位符,也就是tensorFlow的運行時參數,
#x佔位符定義爲28*28=784的數據集,None表示有多個這樣的數據
x = tf.placeholder("float", [None, 784])
#前面介紹過了本例採用的方程算法:W*x+b,
#這裏定義的就是方程中每一項的權重W(weight)
#上面的公式看起來簡單,由於已經矩陣化,實際上
#W包含10個方程式、每一個方程式784個權重值
W = tf.Variable(tf.zeros([784,10]))
#變量b,b比較簡單一些,表明10個方程式中,
#每一個方程最後的常數,b是bias的縮寫
#b是參與運算和返回結果用的,不須要輸入數據,所以是變量不是佔位符
b = tf.Variable(tf.zeros([10]))
#定義核心數學模型
y = tf.nn.softmax(tf.matmul(x,W) + b)
#y_是監督學習中,對應x數據的標註分類標籤
#每一個標籤是10個元素的向量,含義見正文部分
y_ = tf.placeholder("float", [None,10])

#交叉熵代價函數,參考下面正文的解釋
cross_entropy = -tf.reduce_sum(y_*tf.log(y))

#梯度降低法解方程,學習步長是0.01,交叉熵最小時候獲得W和b的解
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)

#循環進行1000個批次的訓練
for i in range(1000):
  #隨機抽取一個批次的訓練數據,抽取算法參考input_data.py
  batch_xs, batch_ys = mnist.train.next_batch(100)
  #開始訓練
  sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
    
#重點來了,在上一個例子中沒有這部分
#訓練結束後(1000個批次後),
#經過驗證集數據驗證咱們模型的正確率
#argmax是內置函數,用於將10個元素的分類表,取出最大的那一維的索引,
#等於將分類變回了0-9數字
#參考正文的解釋,在樣本或者計算結果的分類表中,
#某索引的值若是是1,表示分類到了這一類,算法決定了其他的必然都是0
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
#上面的結果是比較值,因此是true/false這樣的一維數組,
#下面這個公式將bool轉換成0、1數字,求平均值得出最後的正確率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

#顯示驗證組標籤信息
print mnist.test.labels
#執行驗證組正確率運算
#在這個tensorflow任務執行前,必定要理解一個概念,
#就是這個任務,在同一個session中執行,跟上面學習過程的任務是接着的,
#因此實際上計算的核心是相同的一個公式
print sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})

這個模型最後的識別正確率在91%左右,雖然並不高,可是幾行代碼就能獲得這樣的結果,仍是很不錯的了,在不少機器學習應用中,這樣的正確率已經達到應用水平。
上面的代碼經過前面的鋪墊和源碼中的註解,應當很容易理解,額外說一下交叉熵的概念。
代價(cost)函數,也被稱做損失(lost)函數,看名字應當能理解是一回事吧?咱們前面一個例子的代價函數使用了標籤樣本值與計算值相減的平方差,值越小表示越接近方程的正確解。這個模式簡單易懂。不過這種算法有不少缺陷,在這個例子中引入了「交叉熵」的代價函數算法。
交叉熵是個複雜的概念,想詳細瞭解的能夠看參考引文中的softmax連接,其中有比較詳細的解釋。
簡單介紹,就是交叉熵越小(跟平方差算法同樣哈),變量的取值就越肯定,越肯定就表示咱們獲得了肯定的結果。好比曉明考試10次才能及格1次,小王考試10次只會有1次不及格,小李50%的可能及格。那曉明和小王的交叉商就低,肯定性強,小李則最不肯定,你徹底無法判斷他下一次考試是好是差。
在本例中的交叉熵不只僅是計算單一的一組數據,而是本批次100幅圖片的交叉熵的總和。這樣預測表現就比單一數據點計算能更好地描述咱們的求解是否趨近了收斂。

(待續...)

引文及參考

TensorFlow中文社區
手寫字體樣本數據下載
機器學習中訓練集、驗證集和測試集的做用
對線性迴歸、邏輯迴歸、各類迴歸的概念學習
個人機器學習筆記(一) - 監督學習vs 無監督學習
機器學習中訓練集、驗證集和測試集的做用
sigmoid函數
Softmax分類函數

相關文章
相關標籤/搜索