原文地址:machine-learning-is-fun-part-1,原文共分三個部分,筆者在這裏合併到一篇文章中,而且對內容進行了從新排版以方便閱讀。html
本文的Github地址node
筆者的數據科學/機器學習知識圖譜以及系列文章在Github的Repo,歡迎關注與點贊,筆者以前攢了不少零散的筆記,打算拾掇拾掇整理出來git
筆者自大學以來一直斷斷續續的學過機器學習啊、天然語言處理啊等等方面的內容,相信基本上每一個本科生或者研究生都會接觸過這方面,畢竟是一個如此大的Flag。不過一樣的,在機器學習,或者更大的一個概念,數據科學這個領域中,一樣是學了忘忘了學。不能否認,數學是機器學習的一個基石,可是也是無數人,包括筆者學習機器學習的一個高的門檻,畢竟數學差。而在這篇文章中,原做者並無講不少的數學方面的東西,而是以一個有趣實用的方式來介紹機器學習。另外一方面,其實不少數學原理也是頗有意思的,筆者記得當年看完數學之美有一個不小的感觸,就是知道了TF-IDF的計算公式是怎麼來的~github
估計你已經厭煩了聽身邊人高談闊論什麼機器學習、深度學習可是本身摸不着頭腦,這篇文章就由淺入深高屋建瓴地給你介紹一下機器學習的方方面面。本文的主旨便是讓每一個對機器學習的人都有所得,所以你也不能期望在這篇文章中學到太多高深的東西。言歸正傳,咱們先來看看到底什麼是機器學習:web
Machine learning is the idea that there are generic algorithms that can tell you something interesting about a set of data without you having to write any custom code specific to the problem. Instead of writing code, you feed data to the generic algorithm and it builds its own logic based on the data.算法
筆者在這裏放了原做者的英文描述,以幫助更好地理解。Machine Learning便是指可以幫你從數據中尋找到感興趣的部分而不須要編寫特定的問題解決方案的通用算法的集合。通用的算法能夠根據你不一樣的輸入數據來自動地構建面向數據集合最優的處理邏輯。舉例而言,算法中一個大的分類即分類算法,它能夠將數據分類到不一樣的組合中。而能夠用來識別手寫數字的算法天然也能用來識別垃圾郵件,只不過對於數據特徵的提取方法不一樣。相同的算法輸入不一樣的數據就可以用來處理不一樣的分類邏輯。segmentfault
「Machine learning」 is an umbrella term covering lots of these kinds of generic algorithms.數組
粗淺的劃分,能夠認爲機器學習攘括的算法主要分爲有監督學習與無監督學習,概念不難,可是很重要。網絡
假設你是一位成功的房地產中介,你的事業正在蒸蒸日上,如今打算僱傭更多的中介來幫你一塊兒工做。不過問題來了,你能夠一眼看出某個房子到底估值集合,而你的實習生們可沒你這個本事。爲了幫你的實習生儘快適應這份工做,你打算寫個小的APP來幫他們根據房子的尺寸、鄰居以及以前賣的相似的屋子的價格來評估這個屋子值多少錢。所以你翻閱了以前的資料,總結成了下表:app
利用這些數據,咱們但願最後的程序可以幫咱們自動預測一個新的屋子大概能賣到多少錢:
解決這個問題的算法呢就是叫作監督學習,你已知一些歷史數據,能夠在這些歷史數據的基礎上構造出大概的處理邏輯。在將這些訓練數據用於算法訓練以後,通用的算法能夠根據這個具體的場景得出最優的參數,有點像下面這張圖裏描述的一個簡單的智力題:
這個例子裏,你可以知道根據左邊的數字的不一樣推導出不一樣的右邊的數字,那麼你腦子裏就天然而然生成了一個處理該問題的具體的邏輯。在監督學習裏,你則是讓機器幫你推導出這種關係,一旦知道了處理特定系列問題的數學方法,其餘相似的問題也就都能迎刃而解。
咱們再回到最初的那個問題,若是你如今不知道每一個房間的售價,而只知道房間大小、尺寸以及臨近的地方,那咋辦呢?這個時候,就是無監督學習派上用場的時候了。
這種問題有點相似於某人給了你一長串的數字而後跟你說,我不知道每一個數字到底啥意思,不過你看看能不能經過某種模式或者分類或者啥玩意找出它們之間是否是有啥關係。那麼對於你的實習生來講,這種類型的數據有啥意義呢?你雖然不能知道每一個屋子的價格,可是你能夠把這些屋子劃分到不一樣的市場區間裏,而後你大概能發現購買靠近大學城旁邊的屋子的人們更喜歡更多的小臥室戶型,而靠近城郊的更喜歡三個左右的臥室。知道不一樣地方的購買者的喜愛能夠幫助你進行更精確的市場定位。
另外你也能夠利用無監督學習發現些特殊的房產,譬如一棟大廈,和其餘你售賣的屋子差異很大,銷售策略也不一樣,不過呢卻能讓你收穫更多的佣金。本文下面會更多的關注於有監督學習,不過千萬不能以爲無監督學習就可有可無了。實際上,在大數據時代,無監督學習反而愈來愈重要,由於它不須要標註不少的測試數據。
這裏的算法分類仍是很粗淺的,若是要了解更多的細緻的分類能夠參考:
做爲高等智慧生物,人類能夠自動地從環境與經歷中進行學習,所謂熟讀唐詩三百首,不會作詩也會吟,你房子賣多了那天然而然看到了某個屋子也就能知道價格以及該賣給啥樣的人了。這個Strong_AI項目也就是但願可以將人類的這種能力複製到計算機上。不過目前的機器學習算法還沒這麼智能,它們只能面向一些特定的有必定限制的問題。所以,Learning
這個概念,在這裏更應該描述爲:基於某些測試數據找出解決某個問題的等式,筆者也喜歡描述爲對於數據的非線性擬合。但願五十年後看到這篇文章的人,可以推翻這個論述。
基本的思想很好理解,下面就開始簡單的實戰咯。這裏假設你還沒寫過任何機器學習的算法,那麼直觀的來講,咱們能夠編寫一些簡單的條件判斷語句來進行房屋價格預測,譬如:
def estimate_house_sales_price(num_of_bedrooms, sqft, neighborhood): price = 0 # 俺們這嘎達,房子基本上每平方200 price_per_sqft = 200 if neighborhood == "hipsterton": # 市中心會貴一點 price_per_sqft = 400 elif neighborhood == "skid row": # 郊區便宜點 price_per_sqft = 100 # 能夠根據單價*房子大小得出一個基本價格 price = price_per_sqft * sqft # 基於房間數作點調整 if num_of_bedrooms == 0: # 沒房間的便宜點 price = price — 20000 else: # 房間越多通常越值錢 price = price + (num_of_bedrooms * 1000) return price
這就是典型的簡答的基於經驗的條件式判斷,你也能經過這種方法得出一個較好地模型。不過若是數據多了或者價格發生較大波動的時候,你就有心無力了。而應用機器學習算法則是讓計算機去幫你總結出這個規律,大概以下所示:
def estimate_house_sales_price(num_of_bedrooms, sqft, neighborhood): price = <computer, plz do some math for me> return price
通俗的理解,價格比如一鍋燉湯,而臥室的數量、客廳面積以及鄰近的街區就是食材,計算機幫你自動地根據不一樣的食材燉出不一樣的湯來。若是你是喜歡數學的,那就比如有三個自變量的方程,代碼表述的話大概是下面這個樣子:
def estimate_house_sales_price(num_of_bedrooms, sqft, neighborhood): price = 0 # a little pinch of this price += num_of_bedrooms * .841231951398213 # and a big pinch of that price += sqft * 1231.1231231 # maybe a handful of this price += neighborhood * 2.3242341421 # and finally, just a little extra salt for good measure price += 201.23432095 return price
注意,上面那些譬如.841...
這樣奇怪的數據,它們就是被稱爲權重
,只要咱們能根據數據尋找出最合適的權重,那咱們的函數就能較好地預測出房屋的價格。
首先,咱們用一個比較機械式的方法來尋找最佳的權重。
首先將全部的權重設置爲1:
def estimate_house_sales_price(num_of_bedrooms, sqft, neighborhood): price = 0 # a little pinch of this price += num_of_bedrooms * 1.0 # and a big pinch of that price += sqft * 1.0 # maybe a handful of this price += neighborhood * 1.0 # and finally, just a little extra salt for good measure price += 1.0 return price
拿已知的數據來跑一波,看看預測出來的值和真實值之間有多少差距,大概效果以下所示:
咳咳,能夠看出這個差距仍是很大的啊,不過不要擔憂,獲取正確的權重參數的過程漫漫,咱們慢慢來。咱們將每一行的真實價格與預測價格的差價相加再除以總的屋子的數量獲得一個差價的平均值,即將這個平均值稱爲cost
,即所謂的代價函數。最理想的狀態就是將這個代價值歸零,不過基本上不太可能。所以,咱們的目標就是經過不斷的迭代使得代價值不斷地逼近零。
不斷測試不一樣的權重的組合,從而找出其中最靠近零的一組。
很簡單,不是嗎?讓咱們再回顧下你剛纔作了啥,拿了一些數據,經過三個泛化的簡單的步驟獲取一個預測值,不過在進一步優化以前,咱們先來討論一些小小的思考:
過去40年來,包括語言學、翻譯等等在內的不少領域都證實了通用的學習算法也能表現出色,儘管這些算法自己看上去毫無心義。
剛纔咱寫的那個函數也是所謂的無聲的,即函數中,並不知道臥室數目bedrooms、客廳大小square_feet這些變量究竟是啥意思,它只知道輸入某些數字而後得出一個值。這一點就很明顯地和那些面向特定的業務邏輯的處理程序有很大區別。
估計你是猜不到哪些權重纔是最合適的,或許你連本身爲啥要這麼寫函數都不能理解,雖然你能證實這麼寫就是有用的。
若是咱們把參數sqft
改爲了圖片中的像素的敏感度,那麼原來輸出的值是所謂的價格,而如今的值就是所謂的圖片的類型,輸入的不一樣,輸出值的意義也就能夠不同。
言歸正傳,咱們仍是回到尋找最優的權重組合上來。你能夠選擇去帶入全部的可能的權重組合,很明顯是無窮大的一個組合,這條路確定是行不通的。是時候展現一波數學的魅力了,這裏咱們介紹一個數學中常見的優化求值的方法:
首先,咱們將Step 2中提出的代價方程公式化爲以下形式:
而後,咱們將這個代價方程變得更加通用一點:
這個方程就表明了咱們目前的權重組合離真實的權重組合的差距,若是咱們測試多組數據,那麼大概能夠得出以下的數據圖:
圖中的藍色低點即意味着代價最小,也就是權重組合最接近完美值的時候。
有了圖以後是否是感受形象多了?咱們尋找最優權重的過程就是一步一步走到谷底的過程,若是咱們每次小小地修改權重而使得其不斷向谷底靠近,咱們也就在向結果靠近。若是你還記得微積分的一些知識,應該知道函數的導數表明着函數的切線方向,換言之,在圖中的任何一點咱們經過計算函數的導數就知道變化的方向,即梯度降低的方向。咱們能夠計算出代價函數中每一個變量的偏導數而後將每一個當前變量值減去該偏導數,即按照梯度相反的方向前進,那就能夠逐步解決谷底咯。若是你感興趣的話,能夠深刻看看批量梯度降低相關的知識。
若是你是打算找個機器學習的工具庫來輔助工具,那麼到這裏你的知識儲備已經差很少咯,下面咱們再扯扯其餘的東西。
上文提到的所謂三步的算法,用專業的名詞表述應該是多元線性迴歸。即經過輸入含有多個自變量的訓練數據得到一個有效的計算表達式用於預測將來的部分房屋的價格。可是上面所講的仍是一個很是簡單的例子,可能並不能在真實的環境中完美地工做,這時候就會須要下文即將介紹的包括神級網絡、SVM等等更復雜一點的算法了。另外,我還沒提到一個概念:overfitting(過擬合)。在不少狀況下,只要有充足的時間咱們都能獲得一組在訓練數據集上工做完美的權重組合,可是一旦它們用於預測,就會跌破眼鏡,這就是所謂的過擬合問題。一樣的,關於這方面也有不少的方法能夠解決,譬如正則化 或者使用 交叉驗證。
一言以蔽之,儘管基礎的概念很是簡單,仍然會須要一些技巧或者經驗來讓整個模型更好地工做,就好像一個才學完Java基礎的菜鳥和一個十年的老司機同樣。
可能看完了這些,覺着ML好簡單啊,那這麼簡單的東西又是如何應用到圖片識別等等複雜的領域的呢?你可能會以爲能夠用機器學習來解決任何問題,只要你有足夠多的數據。不過仍是要潑點冷水,千萬記住,機器學習的算法只在你有足夠的解決某個特定的問題的數據的時候才能真正起做用。譬如,若是你想依靠某個屋子內盆栽的數目來預測某個屋子的價格,呵呵。這是由於房屋的價格和裏面的盆栽數目沒啥必然聯繫,無論你怎麼嘗試,輸入怎麼多的數據,可能都不能如你所願。
因此,總結而言,若是是可以手動解決的問題,那計算機可能解決的更快,可是它也沒法解決壓根解決不了的問題。在原做者看來,目前機器學習存在的一個很大的問題就是依然如陽春白雪般,只是部分科研人員或者商業分析人員的關注對象,其餘人並不能簡單地理解或者使用,在本節的最後也推薦一些公開的課程給對機器學習有興趣的朋友:
上文中,咱們經過一個簡單的房價預測的例子瞭解了機器學習的基本含義,在本節,咱們將會繼續用一些泛化的算法搭配上一些特定的數據作些有趣的事情。本節的例子大概以下圖所示,一個不少人的童年必備的遊戲:馬里奧,讓咱們用神級網絡幫你設計一些新奇的關卡吧。
在正文以前,仍是要強調下,本文是面向全部對機器學習有興趣的朋友,因此大牛們看到了勿笑。
上文中咱們是使用了多元線性迴歸來進行房屋價格預測,數據格式大概這個樣子:
最後獲得的函數是:
def estimate_house_sales_price(num_of_bedrooms, sqft, neighborhood): price = 0 # a little pinch of this price += num_of_bedrooms * 0.123 # and a big pinch of that price += sqft * 0.41 # maybe a handful of this price += neighborhood * 0.57 return price
若是用圖來表示的話,大概是這個樣子:
不過正如上文中提到的,這個算法只能處理一些較爲簡單的問題,即結果與輸入的變量之間存在着某些線性關係。Too young,Too Simple,真實的房價和這些可不只僅只有簡單的線性關係,譬如鄰近的街區這個因子可能對面積較大和麪積特別小的房子有影響,可是對於那些中等大小的毫無關係,換言之,price與neighborhood之間並非線性關聯,而是相似於二次函數或者拋物線函數圖之間的非線性關聯。這種狀況下,咱們可能獲得不一樣的權重值(形象來理解,可能部分權重值是收斂到某個局部最優):
如今等於每次預測咱們有了四個獨立的預測值,下一步就是須要將四個值合併爲一個最終的輸出值:
咱們將上文提到的兩個步驟合併起來,大概以下圖所示:
咳咳,沒錯,這就是一個典型的神級網絡,每一個節點接收一系列的輸入,爲每一個輸入分配權重,而後計算輸出值。經過鏈接這一系列的節點,咱們就可以爲複雜的函數建模。一樣爲了簡單起見,我在這裏也跳過了不少概念,譬如 feature scaling 以及 activation function,不過核心的概念是:
每一個可以接收一系列的輸入而且可以按權重求和的估值函數被稱爲Neuron(神經元)
多個簡單的神經元的鏈接能夠用來構造處理複雜問題的模型
有點像樂高方塊,單個的樂高方塊很是簡單,而大量的樂高方塊卻能夠構建出任何形狀的物體:
目前,整個神級網絡是無狀態的,即對於任何相同的輸入都返回相同的輸出。這個特性在不少狀況下,譬如房屋價格估計中是不錯的,不過這種模式並不能處理時間序列的數據。舉個栗子,咱們經常使用的輸入法中有個智能聯想的功能,能夠根據用戶輸入的前幾個字符預測下一個可能的輸入字符。最簡單的,能夠根據常見的語法來推測下一個出現的字符,而咱們也能夠根據用戶歷史輸入的記錄來推測下一個出現的字符。基於這些考慮,咱們的神經網絡模型即以下所示:
譬如用戶已經輸入了以下的語句:
Robert Cohn was once middleweight boxi
你可能會猜測是n
,這樣整個詞彙就是boxing
,這是基於你看過了前面的語句以及基本的英文語法得出的推論,另外,middleweight
這個單詞也給了咱們額外的提示,跟在它後面的是boxing
。換言之,在文本預測中,若是你能將句子的上下文也考慮進來,再加上基本的語法知識就能較爲準確地預測出下一個可能的字符。所以,咱們須要給上面描述的神經網絡模型添加一些狀態信息,也就是所謂的上下文的信息:
在神經網絡模型中也保持了對於上下文的追蹤,從而使得該模型不只僅能預測第一個詞是啥,也能預測最有可能出現的下一個詞彙。該模型就是所謂的Recurrent Neural Network:循環神經網絡的基本概念。每次使用神經網絡的同時也在更新其參數,也就保證了可以根據最新的輸入內容預測下一個可能的字符,只要有足夠的內存狀況下,它能夠將完整的時序上下文所有考慮進去。
正如上文所說,文本預測在實際應用中一個典型的例子就是輸入法,譬如iPhone裏面會根據你以前輸入的字符自動幫你補全:
不過咱們作的更瘋狂一點,既然模型能夠根據上一個字符自動預測下一個字符,那咱們何不讓模型來自動構建一個完整的故事? 咱們在這裏使用Andrej Karpathy建立的Recurrent Neural Network implementation框架來進行實驗,他也發表了一系列關於如何使用RNN進行文檔生成的博客。咱們仍是使用The Sun Also Rises
這篇文章,該文章包括大小寫、標點符號等在內一共有84個不一樣的字符以及362239個詞彙。這個數據集合的大小和真實環境中的應用文本相比仍是很小的,爲了儘量模仿原做者的風格,最好的是能有數倍的文本進行訓練,不過做爲例子這邊仍是足夠的。通過大概100次迭代以後,獲得的結果是:
hjCTCnhoofeoxelif edElobe negnk e iohehasenoldndAmdaI ayio pe e h’e btentmuhgehi bcgdltt. gey heho grpiahe. Ddelnss.eelaishaner」 cot AAfhB ht ltny ehbih a」on bhnte ectrsnae abeahngy amo k ns aeo?cdse nh a taei.rairrhelardr er deffijha
慘不忍睹啊,繼續進行訓練,大概1000次迭代以後,內容看起來好一點了:
hing soor ither. And the caraos, and the crowebel for figttier and ale the room of me? Streat was not to him Bill-stook of the momansbed mig out ust on the bull, out here. I been soms inick stalling that aid. 「Hon’t me and acrained on .Hw’s don’t you for the roed,」 In’s pair.」 「Alough marith him.」
已經可以識別基本的句型結構與語法規則咯,甚至可以較好地爲上下文添加標點符號了,不過仍是存在着大量的無心義詞彙,咱們繼續增長訓練的次數:
He went over to the gate of the café. It was like a country bed. 「Do you know it’s been me.」 「Damned us,」 Bill said. 「I was dangerous,」 I said. 「You were she did it and think I would a fine cape you,」 I said. 「I can’t look strange in the cab.」 「You know I was this is though,」 Brett said. 「It’s a fights no matter?」 「It makes to do it.」 「You make it?」 「Sit down,」 I said. 「I wish I wasn’t do a little with the man.」 「You found it.」 「I don’t know.」 「You see, I’m sorry of chatches,」 Bill said. 「You think it’s a friend off back and make you really drunk.」
如今差很少能看了,有些句式仍是很模仿Hemingway’s的風格的,而原做者的內容是:
There were a few people inside at the bar, and outside, alone, sat Harvey Stone. He had a pile of saucers in front of him, and he needed a shave. 「Sit down,」 said Harvey, 「I’ve been looking for you.」 「What’s the matter?」 「Nothing. Just looking for you.」 「Been out to the races?」 「No. Not since Sunday.」 「What do you hear from the States?」 「Nothing. Absolutely nothing.」 「What’s the matter?」
In 2015, Nintendo 宣佈了 Super Mario Maker™ 用於Wii U遊戲系統上。
這個製做器可以讓你去手動製做馬里奧的一些關卡,很不錯的和朋友之間進行互動的小工具。你能夠添加常見的障礙物或者敵人到你本身設計的關卡中,有點像可視化的樂高工做臺。咱們可使用剛纔建立的用於預測Hemingway文本的模型來自動地建立一個超級馬里奧的關卡。首先呢,咱們仍是須要去找一些訓練數據,最先的1985年推出的經典的超級馬里奧的遊戲不錯:
這個遊戲有大概32個關卡,其中70%的場景都有類似的外觀,很適合用來作訓練數據啊。我找來了每一關的設計方案,網上有不少相似的教程教你怎麼從內存中讀取遊戲的設計方案,有興趣的話你也能夠試試。下面呢就是一個經典的全景視圖:
用放大鏡觀察的話,能夠看出每一關都是由一系列的網格狀對象組成的:
這樣的話,咱們能夠將每一個網格中的對象用一個字符代替,而整個關卡的字符化表述就是:
-------------------------- -------------------------- -------------------------- #??#---------------------- -------------------------- -------------------------- -------------------------- -##------=--=----------==- --------==--==--------===- -------===--===------====- ------====--====----=====- =========================-
其中:
-
表明空白
=
表明堅固的方塊
#
表明那些能夠被撞破的塊
?
表明錢幣塊
仔細瞅瞅這個文件,你會發現若是按照一行一行從左到右讀的話,好像是毫無頭緒:
不過若是按照列的次序從上往下讀的話,你會發現仍是有點套路的:
爲了更好地訓練數據,咱們打算按列來分割數據,這裏咱們會使用特徵選擇的技術來將數據轉化爲最合適的表示。首先,咱們將整個文本旋轉90度:
-----------= -------#---= -------#---= -------?---= -------#---= -----------= -----------= ----------@= ----------@= -----------= -----------= -----------= ---------PP= ---------PP= ----------== ---------=== --------==== -------===== ------====== -----======= ---========= ---=========
而後就可使用上面建立好的模型進行訓練咯,通過幾輪訓練以後大概能夠得出這個樣子:
-------------------------- LL+<&=------P------------- -------- ---------------------T--#-- ----- -=--=-=------------=-&--T-------------- -------------------- --=------$-=#-=-_ --------------=----=<---- -------b -
最初的訓練裏模型認知到應該大量的出現-
與=
字符,不過仍是很粗糙,再通過幾千次的訓練,得出的內容是:
-- -----------= ----------= --------PP= --------PP= -----------= -----------= -----------= -------?---= -----------= -----------=
此時模型已經可以認知到須要將每行保證相同的長度,甚至開始尋找出Mario內在的規律:管道呢通常都是兩個塊這麼寬,因此它將全部的P
都放到了2*2的矩陣中,聰明瞭一點啊。繼續學習:
--------PP= --------PP= ----------= ----------= ----------= ---PPP=---= ---PPP=---= ----------=
看上去像模像樣了,其中有幾個須要特別注意的地方:
Lakitu,就是那個小怪獸被放到了半空中,跟Mario關卡同樣同樣的。
它認知到了應該把管道插入大地
並無讓玩家無路可走
看起來風格很是像最傳統的馬里奧的版本
最後生成出來的遊戲截圖大概是這樣的:
你能夠在這裏觀看完整的遊戲視頻。
這裏用於訓練模型的循環神經網絡算法與真實環境下大公司用於解決語音識別以及文本翻譯等常見問題的算法一本同源,而讓咱們的模型看上去好像個玩具同樣的緣由在於咱們的訓練數據。僅僅取自最先期的超級馬里奧的一些關卡數據遠遠不足以讓咱們的模型出類拔萃。若是咱們可以獲取由其餘玩家建立的成百上千的關卡信息,咱們可讓模型變得更加完善。不過惋惜的是咱們壓根獲取不到這些數據。
隨着機器學習在不一樣的產業中變得日漸重要,好的程序與壞的程序之間的差別愈加體如今輸入數據的多少。這也就是爲啥像Google或者Facebook這樣的大公司想方設法地想獲取你的數據。譬如Google最近開源的TensorFlow,一個用於大規模可擴展的機器學習的集羣搭建應用,它自己就是Google內部集羣的重要組成部分。不過沒有Google的海量數據做爲輔助,你壓根建立不了媲美於Google翻譯那樣的牛逼程序。下次你再打開 Google Maps Location History 或者 Facebook Location History ,想一想它們是否是記錄下你平常的東西。
條條大道通羅馬,在機器學習中解決問題的辦法也永遠不止一個。你能夠有不少的選項來決定如何進行數據預處理以及應該用啥算法。加強學習正是能夠幫你將多個單一的方法組合起來的好途徑。若是你想更深刻的瞭解,你能夠參考下面幾篇較爲專業的論文:
Amy K. Hoover’s team used an approach that represents each type of level object (pipes, ground, platforms, etc) as if it were single voice in an overall symphony. Using a process called functional scaffolding, the system can augment levels with blocks of any given object type. For example, you could sketch out the basic shape of a level and it could add in pipes and question blocks to complete your design.
Steve Dahlskog’s team showed that modeling each column of level data as a series of n-gram 「words」 makes it possible to generate levels with a much simpler algorithm than a large RNN.
近年來關於深度學習的討論很是火爆,特別是以前阿爾法狗大戰李世乭以後,更是引起了人們普遍地興趣。南大的周志華教授在《機器學習》這本書的引言裏,提到了他對於深度學習的見解:深度學習掀起的熱潮也許大過它自己真正的貢獻,在理論和技術上並無太大的創新,只不過是因爲硬件技術的革命,從而獲得比過去更精細的結果。相信讀者看完了第三部分也會有所感。
仁者見仁智者見智,這一章節就讓咱們一塊兒揭開深度學習的神祕面紗。在本章中,咱們仍是基於一個實際的例子來介紹下深度學習的大概原理,這裏咱們會使用簡單的卷積神級網絡來進行圖片中的對象識別。換言之,就相似於Google Photos的以圖搜圖的簡單實現,大概最終的產品功能是這個樣子的:
就像前兩章同樣,本節的內容儘可能作到即不雲山霧罩,不知所云,也不陽春白雪,曲高和寡,但願每一個隊機器學習感興趣的人都能有所收穫。這裏咱們不會提到太多的數學原理與實現細節,因此也不能眼高手低,以爲深度學習不過爾爾呦。
先來看一個有趣的漫畫:
這個漫畫可能有點誇張了,不過它的靈感仍是來自於一個現實的問題:一個三歲的小孩可以輕易的辨別出照片中的鳥兒,而最優秀的計算機科學家須要用50年的時間來教會機器去識別鳥兒。在過去的數年中,咱們發現了一個對象識別的好辦法,便是利用深度卷積神級網絡。有點像William Gibson的科幻小說哈,不過只要跟着本文一步一步來,你就會發現這事一點也不神祕。Talk is cheap, Show you the word~
在嘗試怎麼識別照片中的鳥兒以前,咱們先從一些簡單的識別開始:怎麼識別手寫的數字8。在上一章節,咱們瞭解了神級網絡是如何經過鏈式鏈接組合大量的簡單的neurons(神經元)來解決一些複雜的問題。咱們建立了一個簡單的神級網絡來基於牀鋪數目、房間大小以及鄰居的類型來預測某個屋子的可能的價格。
再重述下機器學習的理念,便是一些通用的,能夠根據不一樣的數據來處理不一樣的問題的算法。所以咱們能夠簡單地修改一些神級網絡就能夠識別手寫文字,在這裏簡單起見,咱們只考慮一個字符:手寫的數字8。
大量的數據是機器學習不可代替的前提條件與基石,首先咱們須要去尋找不少的訓練數據。索性對於這個問題的研究已持續了好久,也有不少的開源數據集合,譬如MNIST關於手寫數字的數據集。MNIST提供了60000張不一樣的關於手寫數字的圖片,每一個都是18*18的大小,其中部分關於8的大概是這個樣子:
上章節中構造的神級網絡有三個輸入,在這裏咱們但願用神級網絡來處理圖片,第二步就是須要將一張圖片轉化爲數字的組合,便是計算機能夠處理的樣子。表擔憂,這一步仍是很簡單的。對於電腦而言,一張圖片就是一個多維的整型數組,每一個元素表明了每一個像素的模糊度,大概是這樣子:
爲了可以將圖片應用到咱們的神經網絡模型中,咱們須要將18*18像素的圖片轉化爲324個數字:
此次的共有324個輸入,咱們須要將神經網絡擴大化轉化爲324個輸入的模型:
注意,咱們的神經網絡模型中有兩個輸出,第一個輸出預測該圖片是8的機率,第二個輸出是預測圖片不是8的機率。對於要辨別的圖片,咱們可使用神經網絡將對象劃分到不一樣的羣組中。雖然此次咱們的神經網絡比上次大那麼多,可是現代的電腦仍然能夠在眨眼間處理上百個節點,甚至於可以在你的手機上工做。(PS:Tensorflow最近支持iOS了)在訓練的時候,當咱們輸入一張肯定是8的圖片的時候,會告訴它機率是100%,不是8的時候輸入的機率就是0%。咱們部分的訓練數據以下所示:
雖然咱們上面一直說這個任務不難,不過也沒那麼簡單。首先,咱們的識別器可以對於標準的圖片,就是那些數字端端正正坐在中間,不歪不扭的圖片,能夠很是高效準確地識別,譬如:
不過實際狀況總不會如咱們所願,當那些熊孩子通常的8也混進來的時候,咱們的識別器就懵逼了。
1. Searching with a Sliding Window: 基於滑動窗口的搜索
雖然道路很曲折,可是問題仍是要解決的,咱們先來試試暴力搜索法。咱們已經建立了一個能夠識別端端正正的8的識別器,咱們的第一個思路就是把圖片分爲一塊塊地小區域,而後對每一個區域進行識別,判斷是否屬於8,大概思路以下所示:
這方法叫滑動窗口法,典型的暴力搜索解決方法。在部分特定的狀況下能起到較好地做用,不過效率很是低下。譬如對於同一類型可是大小不一樣的圖片,你可能就須要一遍遍地搜索。
1. More data and a Deep Neural Net
剛纔那個識別器訓練的時候,咱們只是把部分規規矩矩的圖片做爲輸入的訓練數據。不過若是咱們選擇更多的訓練數據時,天然也包含那些七歪八斜的8的圖片,會不會起到什麼神奇的效果呢?咱們甚至不須要去搜集更多的測試數據,只要寫個腳本而後把8放到圖片不一樣的位置上便可:
用這種方法,咱們能夠方便地建立無限的訓練數據。數據有了,咱們也須要來擴展下咱們的神經網絡,從而使它可以學習些更復雜的模式。具體而言,咱們須要添加更多的中間層:
這個呢,就是咱們所謂的深度神經網絡
,由於它比傳統的神經網絡有更多的中間層。這個概念從十九世紀六十年代以來就有了,不過訓練大型的神經網絡一直很緩慢而沒法達到真實的應用預期。不過近年來隨着咱們認識到使用3D圖卡來代替傳統的CPU處理器來進行神經網絡的訓練,使用大型的神經網絡忽然之間就變得再也不那麼高不可攀。
不過儘管咱們能夠依靠3D圖卡解決計算問題,仍然須要尋找合適的解決方案。咱們須要尋找合適的將圖片處理可以輸入神經網絡的方法。好好考慮下,咱們訓練一個網絡用來專門識別圖片頂部的8與訓練一個網絡專門用來識別圖片底部的8,把這兩個網絡分割開來,好像壓根沒啥意義。所以,咱們最終要獲得的神經網絡是要能智能識別不管在圖片中哪一個位置的8。
人們在看圖片的時候通常都會自帶層次分割的眼光,譬以下面這張圖:
你能夠一眼看出圖片中的不一樣的層次:
地上覆蓋着草皮與水泥
有個寶寶
寶寶坐在個木立刻
木馬在草地上
更重要的是,無論寶寶坐在啥上面,咱們都能一眼看到那嘎達有個寶寶。即便寶寶坐在汽車、飛機上,咱們不通過從新的學習也能夠一眼分辨出來。惋惜如今咱們的神經網絡還作不到這一點,它會把不一樣圖片裏面的8當成不一樣的東西對待,並不能理解若是在圖片中移動8,對於8而言是沒有任何改變的。也就意味着對於不一樣位置的圖片仍然須要進行從新學習。咱們須要賦予咱們的神經網絡可以理解平移不變性:無論8出如今圖片的哪一個地方,它仍是那個8。咱們打算用所謂的卷積的方法來進行處理,這個概念部分來自於計算機科學,部分來自生物學,譬如神經學家教會貓如何去辨別圖片。
上面咱們提到一個暴力破解的辦法是將圖片分割到一個又一個的小網格中,咱們須要稍微改進下這個辦法。
1. 將圖片分割爲重疊的磚塊
譬如上面提到的滑動窗口搜索,咱們將原圖片分割爲獨立的小塊,大概以下圖所示:
經過這一步操做,咱們將原始圖片分割爲了77張大小相同的小圖片。
2. 將每一個圖片瓷磚輸入到小的神經網絡中
以前咱們就訓練一個小的神經網絡能夠來判斷單個圖片是否屬於8,不過在這裏咱們的輸出並非直接判斷是否是8,而是處理輸出一個特徵數組:
對於不一樣的圖片的瓷磚塊,咱們都會使用具備相同權重的神經網絡
來進行處理。換言之,咱們將不一樣的圖片小塊都同等對待,若是在圖片裏發現了啥好玩的東西,咱們會將該圖片標識爲待進一步觀察的。
3. 將每一個小塊的處理結果存入一個新的數組
對於每一個小塊輸出的數組,咱們但願依然保持圖片塊之間的相對位置關聯,所以咱們將每一個輸出的數組仍然按照以前的圖片塊的次序排布:
到這裏,咱們輸入一個大圖片,輸出一個相對而言緊密一點的數組,包含了咱們可能剛興趣的塊的記錄。
4. 縮減像素採樣
上一步的結果是輸出一個數組,會映射出原始圖片中的哪些部分是咱們感興趣的。不過整個數組仍是太大了:
爲了縮減該特徵數組的大小,咱們打算使用所謂的max pooling算法來縮減像素採樣數組的大小,這算法聽起來高大上,不過仍是挺簡單的:
Max pooling處理過程上呢就是將原特徵矩陣按照2*2分割爲不一樣的塊,而後從每一個方塊中找出最有興趣的位保留,而後丟棄其餘三個數組。
5. 進行預測
截至目前,一個大圖片已經轉化爲了一個相對較小地數組。該數組中只是一系列的數字,所以咱們能夠將該小數組做爲輸入傳入另外一個神經網絡,該神經網絡會判斷該圖片是否符合咱們的預期判斷。爲了區別於上面的卷積步驟,咱們將此稱爲fully connected
網絡,整個步驟呢,以下所示:
6. 添加更多的步驟
上面的圖片處理過程能夠總結爲如下步驟:
Convolution: 卷積
Max-pooling: 特徵各維最大彙總
Full-connected: 全鏈接網絡
在真實的應用中,這幾個步驟能夠組合排列使用屢次,你能夠選擇使用兩個、三個甚至十個卷積層,也能夠在任什麼時候候使用Max-pooling來減小數據的大小。基本的思想就是將一個大圖片不斷地濃縮直到輸出一個單一值。使用更多地卷積步驟,你的網絡就能夠處理學習更多地特徵。舉例而言,第一個卷積層能夠用於識別銳邊,第二個卷積層可以識別尖銳物體中的鳥嘴,而第三個卷積層能夠基於其對於鳥嘴的知識識別整個鳥。下圖就展現一個更現實點地深度卷積網絡:
在這個例子中,最先地是輸入一個224*224像素的圖片,而後分別使用兩次卷積與Max-pooling,而後再依次使用卷積與Max-pooling,最後使用兩個全鏈接層。最後的結果就是圖片被分到哪一類。
概念瞭解了,下面咱們就動手寫一個真正的鳥類分類器。一樣地,咱們須要先收集一些數據。免費的 CIFAR10 data set包含了關於鳥兒的6000多張圖片以及52000張不是鳥類的圖片。若是不夠,Caltech-UCSD Birds-200–2011 data set 中還有12000張鳥類的圖片。其中關於鳥類的圖片大概以下所示:
非鳥類的圖片大概這樣:
這邊咱們會使用TFLearn來構建咱們的程序,TFLearn是對於Google的TensorFlow 深度學習庫的一個包裹,提供了更易用的API,可讓編寫卷積神經網絡就好像編譯咱們其餘的網絡層同樣簡單:
# -*- coding: utf-8 -*- """ Based on the tflearn example located here: https://github.com/tflearn/tflearn/blob/master/examples/images/convnet_cifar10.py """ from __future__ import division, print_function, absolute_import # Import tflearn and some helpers import tflearn from tflearn.data_utils import shuffle from tflearn.layers.core import input_data, dropout, fully_connected from tflearn.layers.conv import conv_2d, max_pool_2d from tflearn.layers.estimator import regression from tflearn.data_preprocessing import ImagePreprocessing from tflearn.data_augmentation import ImageAugmentation import pickle # Load the data set X, Y, X_test, Y_test = pickle.load(open("full_dataset.pkl", "rb")) # Shuffle the data X, Y = shuffle(X, Y) # Make sure the data is normalized img_prep = ImagePreprocessing() img_prep.add_featurewise_zero_center() img_prep.add_featurewise_stdnorm() # Create extra synthetic training data by flipping, rotating and blurring the # images on our data set. img_aug = ImageAugmentation() img_aug.add_random_flip_leftright() img_aug.add_random_rotation(max_angle=25.) img_aug.add_random_blur(sigma_max=3.) # Define our network architecture: # Input is a 32x32 image with 3 color channels (red, green and blue) network = input_data(shape=[None, 32, 32, 3], data_preprocessing=img_prep, data_augmentation=img_aug) # Step 1: Convolution network = conv_2d(network, 32, 3, activation='relu') # Step 2: Max pooling network = max_pool_2d(network, 2) # Step 3: Convolution again network = conv_2d(network, 64, 3, activation='relu') # Step 4: Convolution yet again network = conv_2d(network, 64, 3, activation='relu') # Step 5: Max pooling again network = max_pool_2d(network, 2) # Step 6: Fully-connected 512 node neural network network = fully_connected(network, 512, activation='relu') # Step 7: Dropout - throw away some data randomly during training to prevent over-fitting network = dropout(network, 0.5) # Step 8: Fully-connected neural network with two outputs (0=isn't a bird, 1=is a bird) to make the final prediction network = fully_connected(network, 2, activation='softmax') # Tell tflearn how we want to train the network network = regression(network, optimizer='adam', loss='categorical_crossentropy', learning_rate=0.001) # Wrap the network in a model object model = tflearn.DNN(network, tensorboard_verbose=0, checkpoint_path='bird-classifier.tfl.ckpt') # Train it! We'll do 100 training passes and monitor it as it goes. model.fit(X, Y, n_epoch=100, shuffle=True, validation_set=(X_test, Y_test), show_metric=True, batch_size=96, snapshot_epoch=True, run_id='bird-classifier') # Save model when training is complete to a file model.save("bird-classifier.tfl") print("Network trained and saved as bird-classifier.tfl!")
若是你有足夠的RAM,譬如Nvidia GeForce GTX 980 Ti或者更好地硬件設備,大概能在1小時內訓練結束,若是是普通的電腦,時間要耗費地更久一點。隨着一輪一輪地訓練,準確度也在不斷提升,第一輪中準確率只有75.4%,十輪以後準確率到91.7%,在50輪以後,能夠達到95.5%的準確率。
咱們可使用以下腳本進行圖片的分類預測:
# -*- coding: utf-8 -*- from __future__ import division, print_function, absolute_import import tflearn from tflearn.layers.core import input_data, dropout, fully_connected from tflearn.layers.conv import conv_2d, max_pool_2d from tflearn.layers.estimator import regression from tflearn.data_preprocessing import ImagePreprocessing from tflearn.data_augmentation import ImageAugmentation import scipy import numpy as np import argparse parser = argparse.ArgumentParser(description='Decide if an image is a picture of a bird') parser.add_argument('image', type=str, help='The image image file to check') args = parser.parse_args() # Same network definition as before img_prep = ImagePreprocessing() img_prep.add_featurewise_zero_center() img_prep.add_featurewise_stdnorm() img_aug = ImageAugmentation() img_aug.add_random_flip_leftright() img_aug.add_random_rotation(max_angle=25.) img_aug.add_random_blur(sigma_max=3.) network = input_data(shape=[None, 32, 32, 3], data_preprocessing=img_prep, data_augmentation=img_aug) network = conv_2d(network, 32, 3, activation='relu') network = max_pool_2d(network, 2) network = conv_2d(network, 64, 3, activation='relu') network = conv_2d(network, 64, 3, activation='relu') network = max_pool_2d(network, 2) network = fully_connected(network, 512, activation='relu') network = dropout(network, 0.5) network = fully_connected(network, 2, activation='softmax') network = regression(network, optimizer='adam', loss='categorical_crossentropy', learning_rate=0.001) model = tflearn.DNN(network, tensorboard_verbose=0, checkpoint_path='bird-classifier.tfl.ckpt') model.load("bird-classifier.tfl.ckpt-50912") # Load the image file img = scipy.ndimage.imread(args.image, mode="RGB") # Scale it to 32x32 img = scipy.misc.imresize(img, (32, 32), interp="bicubic").astype(np.float32, casting='unsafe') # Predict prediction = model.predict([img]) # Check the result. is_bird = np.argmax(prediction[0]) == 1 if is_bird: print("That's a bird!") else: print("That's not a bird!")
剛纔有提到,咱們的程序有95%的準確度,不過這並不意味着你拿張圖片來,就確定有95%的機率進行準確分類。舉個栗子,若是咱們的訓練數據中有5%的圖片是鳥類而其餘95%的都不是鳥類,那麼就意味着每次預測其實不是鳥類的準確度達到95%。所以,咱們不只要關注總體的分類的準確度,還須要關注分類正確的數目,以及哪些圖片分類失敗,爲啥失敗的。這裏咱們假設預測結果並非簡單的正確或者錯誤,而是分到不一樣的類別中:
首先,咱們將正確被標識爲鳥類的鳥類圖片稱爲:True Positives
其次,對於標識爲鳥類的非鳥類圖片稱爲:True Negatives
對於劃分爲鳥類的非鳥類圖片稱爲:False Positives
對於劃分爲非鳥類的鳥類圖片稱爲:False Negatives
最後的值能夠用以下矩陣表示:
這種分法的一個現實的意義譬如咱們編寫一個程序在MRI圖片中檢測癌症,false positives的結果很明顯好於false negatives。False negatives意味着那些你告訴他沒得癌症可是人家就是癌症的人。這種錯誤的比例應該着重下降。除此以外,咱們還計算Precision_and_recall來衡量總體的準確度: