機器學習中如何做單元測試(Unit Test)來檢測模型穩定性?
機器學習代碼的典型特徵就是對於數據依賴性強,從而使得測試變得很困難,特別是中間代碼(依賴之前處理得到的數據)。是不是有比較有效的方法來做單元測試?如果不是用單元測試,有什麼好方法來知道自己寫的某一段代碼是正確的?
不邀自來:)
首先機器學習的確不容易做單元測試(又叫模塊測試)。根本原因就是:單元測試的重要前提就是可重複性和確定性,而大部分機器學習模型本質上就是低穩定性和高隨機性的。
據我所知,很多數據模型都是開發完就上線了,並沒有配套的單元測試。當然這種現象的發生也有主觀性,那就是作為一個處於快速發展中的領域並未建立行業標準,且從業者水平參差不齊。
但退一步說,並不是每個機器學習模型都需要單元測試,我們要警惕這種程序員思維。當然,框架類和底層模型沒有配套的單元測試是不行的,但大部分數據分析項目並不值得花大力氣來做單元測試。小型的數據挖掘項目只要使用少量的測試來保證關鍵部分的正確性,再加上良好的數據可視化,不必投入大精力去做完整的單元測試,避免買櫝還珠。我們需要在性能、穩定性、成本投入之間找到一個平衡點。
答案的結構如下:
- 1. 討論為什麼機器學習的單元測試不容易?
- 2. 單元測試有什麼好處 什麼情況適合做單元測試 ?
- 3. 一些對於機器學習單元測試的建議和技巧。
讓我們進入今天的正片環節。
-------------------------------------------------------------------------------------
1. 為什麼機器學習模型不好做單元測試?
核心問題就是因為機器學習缺乏穩定性,導致其中間過程和最終結果不平穩(*部分截取自我的專欄文章[1])。而機器學習中很難得到穩定的模型,原因如下:
- 1.1. 計算的穩定性(Computational Stability): 特指模型運算性能的魯棒性(Robustness)。舉個簡單例子,如果我們讓整數型(int)的變數來儲存的一個浮點變數(float),那麼我們會損失精度。在機器學習中,我們往往涉及了大量的計算,受限於計算機的運算精度,很多時候我們必須進行湊整(Rounding),將無理數近似到浮點數。這個過程中不可避免的造成了大量的微小誤差,隨著湊整誤差累計積少成多,最終會導致系統報錯或者模型失敗。
- 1.1.1. 下溢(Underflow)和上溢(Overflow): 溢出是代表內容超過了容器的極限。在機器學習當中,因為我們大量的使用概率(Probability),而概率的區間往往在0至1之間,這就導致了下溢發生的可能性大大提高。舉個簡單的例子,機器學習中往往是成百上千個數字相乘,類似的情況導致計算機無法分辨0和和一個極小數之間的區別。相似的,上溢也是很容易發生的狀況。試想我們需要將多個較大的數相乘,很輕易的就可以超過計算機的上限。
- 1.1.2. 平滑(Smoothing)與0: 和下溢和上溢類似,我們會發現機器學習中常常遇到「連乘式」中某個元素為0,導致運算失去意義。以樸素貝葉斯(Naive Bayes)為例: 中存在多項可能為0的地方,且輸出是乘積。只要其中任一元素為0那麼結果就為0。然而出現0往往並不是真的因為其概率為0,而僅僅是我們的訓練數據沒有出現過。從某種意義上來說,這也屬於一種計算上的不穩定,即少量的數據變動可造成大量的結果變化。
- 1.2. 演算法穩定性(Algorithmic Stability)與擾動(Perturbation): 我們要考慮機器學習演算法對於數據擾動的魯棒性。相信關注專欄的讀者應該已經聽我無數次提起過:「模型的泛化誤差由誤差(Bias)和方差(Variance)共同決定,而高方差是不穩定性的罪魁禍首」。簡單的說,如果一個演算法在輸入值發生微小變化時就產生了巨大的輸出變化,我們就可以說這個演算法是不穩定的。此處的演算法不僅僅是說機器學習演算法,也代表「中間過程」所涉及的其他演算法,具體的例子包括: a. 矩陣求逆(Inverting a Matrix)的過程 b. 神經網路中的批量學習(Batch Learning)的尺寸和學習速率 c. 決策樹。當然,穩定的機器學習模型是存在的,比如各種支持向量機(SVM)的衍生模型,這也是SVM在本世紀初大火的原因的之一:)
- 1.3. 數據的穩定性(Data Stability): 嚴格意義上說,數據穩定性往往特指的是時間序列(Time Series)的穩定性。而筆者此處指的是廣義上的數據,不僅僅是時間序列。從根本上說,數據的穩定性主要取決於其Variance。一個機器學習模型的泛化能力指的是其在新樣本上的擬合能力。模型能夠獲得強泛化能力的數據保證就是其訓練數據是獨立同分布從母體分布上採樣而得。因此數據的穩定性的基本前提就是獨立同分布,且數量越多越好。穩定的數據可以保證模型的經驗誤差(Empirical Risk)約等於其泛化誤差(Generalization Risk)。
在實際過程中,我們很少能遇到同時滿足以上穩定性要求的機器學習項目,這使得單元測試的可操作性變得很低。
2. 機器學習單元測試有什麼好處 什麼情況下可以做單元測試?
機器學習單元測試的好處和軟體開發的單元測試是相類似的,即快速定位到模型中的問題。幾乎每個機器學習模型都可以進行單元測試,只是面臨不同程度的工作量,且意義不同。像前文說到的,我不建議每個機器學習問題都去做單元測試,在數據量小或時間成本太高的前提下可以略過。
然而,機器學習框架(framework)是一定要有單元測試的,如Tensorflow、Theano之類的深度學習框架。不過從這一類框架的單元測試也可以看到其測試邏輯: 模塊最小化。因此想要學習機器學習模塊測試,我建議看Keras的代碼實現。
以Keras(fchollet/keras)為例,它的test分成了很多小模塊,比如對loss function的測試模塊,對激活函數的測試,對regularizer的測試。整個框架就是多個小模塊的排列組合。還有一點值得關注的,那就是在測試的時候後,keras都使用了獨立的數據集。
對於非框架類的機器學習如果機器學習模型符合以下幾個條件,我們可以比較高效的進行測試:
- 2.1. 數據量充足:
- 這個前提可以使我們首先相信訓練的模型已經有了較強的泛化能力,也就是說我們的模型表現已經比較趨於穩定,容易判定測試失敗。
- 同時這也說明了我們有「多餘」的數據可以用於測試。當然,單元測試是否可以重複使用訓練數據集還可以打個問號,這取決於我們對於單元測試的期待值。
- 2.2. 模型局部穩定且全局複雜: 如果所選模型的穩定性比較好,那麼可以簡化單元測試的難度,不需要對每一小步都進行測試。比如上文提到支持向量機(SVM)就是比較好的例子。但換句話說,對於比較穩定的模型,使用單元測試的意義也變小了。因此,如果一個問題總體來說比較複雜,但每一個小步驟都比較穩定,那麼還是可以做單元測試的。
- 2.3. 時間和預算充足
至於其他類型的小型機器學習項目,在時間不允許的情況下,似乎沒有必要有做太完善的模塊測試,更直觀的每次可視化結果就可以了。
3. 如何有效的進行模塊測試?
- 3.1. 模塊拆分:最重要的就是必須有良好的API設計,盡量將可測試的部分做到小模塊。舉個例子,優化時的梯度下降計算梯度,我們應該把「計算梯度」作為一個單獨的小測試,而不該把這個算作一個大測試的一部分。因此,盡量避免直接檢查一堆複雜操作的最終結果,在不穩定的機器學習過程中隨機性會被疊成放大。
- 3.2. 有清晰的測試標準: 在設計機器學習單元測試前,請務必對你要測試的模塊有充分的了解,知道其大概的誤差值範圍或者說取值範圍。這也是設計時的難點,因為設計通過標準需要對領域的深刻理解。
- 3.3. 放寬標準允許誤差: 與軟體開發不同,我們一再強調機器學習處處都是隨機和不確定性。因此在標準上百分之九十九的情況請不要用「等於」來測試一個值,而是使用「大於等於」,「小於等於」之類的範圍標準來檢測。
- 3.4. 可視化複雜模塊表現: 因為絕對數值的對比難度高,我們建議可以用可視化來進行複雜模塊的對比。舉例,如果我們想直接對比一個複雜模塊的內容是否發生了大的變化,最好的方法還是直接繪圖來對比。以 Dominodatalab的例子來說,我們可以很快看到兩次運行之間的巨大不同,這時我們就會警鈴大作。
- 3.5. 引入外部數據集: 數據為金的年代,將寶貴的數據花在單元測試上似乎有點浪費。如果我們的模塊化拆分做的足夠小,那麼可以引用外部數據集來進行驗證,而不必浪費真實數據。
- 3.6. 模擬數據進行測試: 跟3.5.想說的一樣,在數據非常寶貴的前提下,可以對數據分布進行建模來模擬出測試數據。當然,我們需要理解這樣做的風險是判定測試是否成功的範圍需要放的更寬。
- 3.6. 使用有成熟測試模塊的語言: 作為一個Python和R都比較熟悉的人,我在需要有完整測試的時候,會傾向於使用Python作為開發語言。同理,Java和C++也比較適合於開發複雜的框架類模型(為了速度還是C++),在這種情況下使用R明顯就不是最好的選項。
-------------------------------------------------------------------------------------
[1] 帶你了解機器學習(二): 機器學習中的amp;amp;amp;amp;amp;amp;amp;quot;穩定性amp;amp;amp;amp;amp;amp;amp;quot;
-------------------------------------------------------------------------------------
安利一下最近的回答和文章:
阿薩姆:如何評價多倫多大學新建的向量學院 (Vector Institute)?對人工智慧領域會有何影響?
阿薩姆:如何有效處理特徵範圍差異大且類型不一的數據?
阿薩姆:反欺詐(Fraud Detection)中所用到的機器學習模型有哪些?
阿薩姆:如何看待「機器學習不需要數學,很多演算法封裝好了,調個包就行」這種說法?
帶你了解機器學習(一): 機器學習中的「哲學」
測試驅動開發。
無論你開發的是什麼樣的功能,什麼樣的模塊,終究分拆到方法或者函數的時候,是有他明確的功能性邏輯的。在你代碼開發前,你就應該已經知道輸入值和預期輸出,所以根據你的功能邏輯,你完全可以從不同緯度設計你的「單元測試用例」。一旦你的方法或者函數編碼完成,你接下去就需要去編寫對應的驅動模塊和樁模塊,把測試用例導進去進行驗證。說白了,就是寫一段驗證代碼驗證你的代碼。如果你實現的PCA在低維的數據上是正確的,那麼在高維數據上一定也是正確的,因為PCA演算法沒有使用諸如迭代優化之類的具有不確定性的方法,這也是它很高效的原因。舉個更簡單的例子,如果讓你實現一個多維空間的a+b的演算法,你在三維空間中的測試的正確性應該可以毫無疑問地推廣到更高維的空間吧?
所以,首先你要弄清楚你實現的演算法中,哪些步驟是帶有不確定性的,哪些不是。像矩陣的基本運算,線性優化之類都可以是不帶有不確定性的,低維空間的測試結果完全可以推廣到高維空間,而所有的非線性優化都是帶有不確定性的,從演算法的原理上就無法保證每次都能得到最優解。- 對於那些不帶有不確定性的步驟,完全可以將低維小數據量的測試正確性推廣到高維大數據量。
- 對於帶有不確定性的步驟,確實比較頭疼,但不確定性只是在原理上,你要測試的是代碼是否實現正確而已(代碼實現上也可能帶有不確定性嗎?直覺告訴我不可能,但我不敢下結論)。對於這樣的演算法,你不可以直接將其作為一個黑盒來測試,而是要再將其細分,直到每一部分都不再帶有不確定性。以最速梯度下降法為例,由於它是一個局部優化演算法,所以無法保證你能每次都得到最優解,自然無法簡單地通過測試它的優化結果是否為最優來確定它在實現上的正確性。而且,用它來實現的優化演算法在低維空間中的效果要比高維好很多,所以,如果把它當做一個黑盒看待,在低維空間中的測試正確性難以推廣到高維。但是,單純從代碼上看還是可以通過更加細緻地測試來確定其實現的正確性的。最速梯度下降法在實現上可以分為目標函數,目標函數的梯度函數以及迭代函數這三個部分。將這三個部分分開來進行測試就比較簡單了。事實上,你自己所說的那個「最傻的辦法」其實不就是這樣么?你把所有的中間結果列印出來,看它們是否在你自己估計的路徑上,這本身也就是測試呀。如果你把這個過程自動化不就變成自動的單元測試了么。
其實我覺得最要命的」不確定性「大多來源於項目所使用的第三方代碼,不是自己寫的,不了解細節,所以難以測試。
我不知道你有沒有做過UFLDL上的作業。如果沒有你可以去做做,或者下載下來看一看作業的模板。
Andrew Ng對代碼做了一個很好的分割(設計良好的函數介面),以及利用一些額外的演算法(好比梯度檢驗)來對每一部分的代碼進行測試,我覺得這些是值得學習和借鑒的。
機器學習里本身大家都會設val啊,k折驗證法某個角度來說就是一種變向的測試啊。訓練結束後得到的模型,只要保證輸入格式沒問題,出來的結果肯定不會出錯啊。當然你如果要assert的話,似乎沒太多好法子,畢竟模型不同,得到的單一結果也不同,更好的測試方法依然是輸入足夠多的新數據,看正確率究竟能否滿足某個閾值。
看模型,模型不同有不同的技巧。先佔坑
我的做法是隨機生成100萬份數據樣例做測試,如果全部通過,那就默認沒問題了
推薦Cosma Shalizi教授的lecture:http://www.stat.cmu.edu/~cshalizi/statcomp/13/lectures/07/lecture-07.pdf
機器學習代碼的典型特徵才不是對於數據依賴性強,而是根本不知道如何debug實際上我只推薦「做github的搬運工「,除了一些線性模型,我不認為有幾個人讀過paper以後能復現文中的演算法
推薦閱讀:
※怎麼畫出含最大最小平均值的圖,即帶標準偏差的曲線圖?
※深度為n的滿二叉樹中選擇k個點,不能有直系親屬關係,共有多少種選法?
※求推薦分位數回歸的基礎書籍或文獻?
※什麼是「長尾效應」 ?
※為什麼用標準差而不是平均差來反映數據的離散程度?