<EYD與機器學習>二:End-to-End Machine Learning Project
各位知乎兒大家好,這是<EYD與機器學習>專欄的第二篇文章,這篇文章以《Hands-on Machine Learning with Scikit-Learn and TensorFlow》(後面簡稱為HMLST)第二章的內容為主線,其間會加入我們成員的一些經驗和思考與大家分享。
在這一章,我們將以「加州房價預測」這一問題來完成一個端到端的機器學習項目,這個問題並不難,可以說是機器學習領域的「Hello world!」,但是我們的側重點並不在於這個問題本身,而在於當你拿到這個問題時需要以什麼樣的思路和流程來審視它,希望大家在看過這篇文章後能夠在這方面有所收穫。
第二章:End-to-End Machine Learning Project
2.1 使用通用數據集
在我們學習過程中,數據集是必不可少的,針對不同的演算法和任務,選擇合適的數據集是很重要的,這裡為大家列舉了一些通用數據集:
·UC Irvine Machine Learning Repository (UCI Machine Learning Repository)
·Kaggle datasets (Datasets | Kaggle)
·Amazons AWS datasets (AWS Public Datasets)
·THE MNIST DATABASE of handwritten digits(MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges)
·SUN database(SUN Database)
·ImageNet(ImageNet)
這裡僅列舉這些,現在流行的數據集有很多,大家需要根據自己的實際需求來選擇適合自己的數據集。
2.2 從全局審視問題
加州房價預測:利用加州人口普查數據,數據里包括人口數量、房價中位數、收入中位數和房屋裡總房間數等特徵,建立模型來預測給定的加州某一地區的房價的中位數。
在看到這個問題時,你可能覺得很簡單。的確,這個問題並不難,但是請在腦海里想一下,這個問題的解決需要哪些步驟?每個步驟需要用到哪些知識和技術?最終你的演算法會反饋給你的是什麼?
如HMLST作者所說,作為一個數據科學家或者相關從業者,在拿到一個問題或項目時,第一件事就是列出你的「機器學習工程清單」。書中的附錄B給出了作者的參考清單,裡面包含了解決一個項目的完整流程。當然,這個清單不是固定的,在面對不同的問題時需要自己制定這個清單。希望大家能夠注意這個過程,對於大多數情況,知道做什麼要比知道怎麼做重要的多,綱舉目張,之後的事情就好辦很多。下文的流程就是作者清單中給出的步驟,希望大家能參考和改正。
2.2.1 構造當前問題
當你是一名學生時,你可以很簡單的看待這個問題,只需要設計高精度的演算法就可以。然而在實際工作中卻不是這麼簡單,現在假如你是一名演算法工程師,那麼首先需要向上級詢問清楚他有哪些具體要求,因為建立一個精確的模型可能並不是最終目的,而且你的工作可能也不是獨立的,這些條件都會影響你對問題的構造以及後續演算法的選擇。
在你的詢問下,Boss告訴你模型的輸出還會和其他信息一起輸入到另一個機器學習系統中,其全部流程如下:
房地產投資機器學習系統(HMLST figure 2-2)
如圖(HMLST figure 2-2)所示,你的工作是完成房地產投資系統中間的一個環節,這時就需要思考兩個問題:
1、上游環節會提供給你哪些信息和數據?
2、下游環節需要你提供給他們哪些信息和數據?
這兩個問題是十分重要的,它們直接決定了你的任務類型。比如,上游部門提供給你的是無標籤數據,那麼你的模型就是無監督的,反之就為有監督的;下游部門需要你提供給他的是具體的房價,那麼你的任務就是構建回歸模型,假如它們需要的是房價的標籤「高、中、低」,那麼你的任務就是構建分類模型。
現在得到消息,上游部門提供給你多特徵帶標記的數據,下游部門需要你提供能夠預測具體房價的模型。這樣就可以為你的模型定性了:有監督、回歸模型,也就是一個多元回歸模型,這樣就構造了當前任務。
2.2.2 選擇性能度量
接下來就是選擇一個合適的性能度量來衡量你的模型的性能如何,對於一個回歸問題,均方根誤差(Root Mean Square Error )是一個典型的性能度量。公式如下 :
雖然均方根誤差很經典,但是在某些情況下其他的性能度量也會用到,比如絕對平均誤差(Mean Absolute Error ):
可以看出,RMSE和MAE都是計算兩個向量的距離,分別使用二範數和一範數。範數的指數越高,範數就會更加關注向量里的較大的值而忽略較小的值。例如
2.3 獲取數據
2.3.1 搭建工作環境
首先,本書作者使用的是Python3,所以需要安裝Python3.x,大家可以在Python的官網下載( Welcome to Python.org)。作者提供的示常式序使用到了Jupyter notebook ,這是一個很好的演示工具,網上有很多使用教程,大家可以在官網(Project Jupyter)學習,或者這裡的一個教程
Getting started with the Jupyter notebook (part 1)本書中,作者經常使用的框架和工具包有:
·Scikit-learn(scikit-learn: machine learning in Python)
·TensorFlow(https://www.tensorflow.org/)
·Matplotlib (Installation - Matplotlib 2.2.2 documentation)
·Numpy(NumPy - NumPy)
·Pandas(Python Data Analysis Library)
這些框架和包都可以通過pip指令安裝,還是很方便的。
2.3.2 下載和瀏覽數據
藉助安裝的框架,很方便地就可以下載數據
>>import pandas as pd
>>def load_housing_data(housing_path=HOUSING_PATH):
csv_path = os.path.join(housing_path, "housing.csv")
return pd.read_csv(csv_path)
之後使用load_housing_data()函數:
>>housing = load_housing_data()
這裡是通過Pandas這個工具來下載數據,數據的類型是Pandas的 DataFrame,所以有一些專有屬性,可以很方便的查看數據的一些特徵。通過head()方法可以查看前五行的數據和特徵名稱:
>>housing.head()
這裡可以清楚的看到特徵的種類,個數和類型,對數據有一個直觀的認識。其中大多數特徵為數值型,但是也有非數值型特徵(ocean_proximity)。
通過info()方法可以查看各個特徵的數據類型和數據的個數:
>>Housing.info: home()
可以看到大多數的數據類型為float64,並且數據的個數為20640,但是total_bedrooms這個特徵出現了缺失,只有20433個數據,這需要我們進行一些特殊處理,在第一章中我們已經接觸到了一些處理缺失數據的方法。同時,ocean_proximity這個特徵的type為object,我們不能只通過head()和info()這兩個函數獲得它的完整信息,這裡我們可以利用value_counts() :
>> housing["ocean_proximity"].value_counts()
這樣我們就可以清楚的知道ocean_proximity這個特徵中共有四類,並且每一類的數量也知道了。以上都是一些直觀的角度,接下來我們可以分析一下各個特徵的統計信息,這裡使用describe() 函數,只針對數值型特徵:
>>housing.describe()
這樣我們就可以獲得各個數值型特徵的均值,極值,標準差,中位數等信息,這對我們後續的數據處理例如標準化會很有幫助。
上面的處理都是以數據的形式來展現各個特徵的信息,但是使用圖會更加直觀,下面繪製各個特徵的直方圖,作者只是簡單地使用了hist() :
>>housing.hist(bins=50, figsize=(20,15))
>>plt.show()
這樣我們可以看到數據的分布情況,作者這個圖有一些不恰當的地方,比如一些數據的橫軸被限幅了,另外,由於數據本身的問題,有些特徵的圖分布極其不均勻,或許可以考慮將橫軸變為log尺度。雖然這些圖可能不是很好的統計圖,但是它卻可以提醒你後續需要做的很多事情:各個特徵的數值範圍差異很大,可能有幾百倍的差別;在某些特徵內部,數據的分布很不均勻,這就需要我們對這些極端值的處理。
2.3.3 劃分出測試集
在經過上述的簡單分析之後,已經對數據有了初步的了解,接下來是否應該上演算法了呢?當然不是,因為還沒有劃分出部分數據作為數據集。也許你會認為劃分數據集很簡單:隨機的從全部數據中挑選出20%(或其他比例的數據)保留起來。其實不盡然,試想我們的模型不會一蹴而就,那麼在不斷的調試過程中,每次都需要重複挑選測試集這一過程,那麼在多次試驗之後,模型可能就「見過」所有的數據了。所以,需要在每次運行時保證測試集不變,這裡作者提供了兩種方法:第一是使用隨機數種子,每次產生相同的隨機數進行挑選;第二是利用數據本身的特性,這裡作者是使用每個樣本辨識信息的hash的最後一位作為每個樣本的標記的,這個標記不會改變。具體程序大家可以參考示例代碼。
到這裡我們並沒有完成測試集的劃分,試想,隨機挑選20%的數據,假如這些數據大部分集中在某個極值的附近怎麼辦?這會對模型的測試產生不好的影響,並且某些特徵可能很重要並且是層次清晰的,那麼我們就不該對數據採取隨機採樣,而應選擇分層採樣,作者列舉了一個例子,將median_income 這個特徵劃分為五個層次,分別進行隨機採樣和分層採樣,之後比較採樣得到的兩個測試集和全部median_income數據各層次所佔比例的差異:
可以看出,分層採樣更能體現原始數據的特性。
2.4 數據挖掘及可視化
在獲取數據後的初始階段,通常的做法會選擇快速瀏覽一下所有的數據,以便對數據一些直觀的特點有所了解,如:數據的類型,數據集的大小,數據的特徵數目等等。前面已經很好的闡述了如何初步了解數據的過程。 接下來我們的目的就是要進一步挖掘數據的內部特徵,內在聯繫。
首先,除了在最後的模型測試階段以外,我們都要確保測試集沒有參與到我們的任何數據處理過程當中。在這裡,我們先將測試集完好的放在一邊,然後對訓練集進行分析。處理過程中有兩個重要的點需要注意。第一,就是當訓練集規模很大的時候,為了提高操作效率,可以對整個訓練集進行採樣後處理。當然在訓練集不那麼龐大的情況下,最好能用上所有的數據,以免遺漏重要的信息。第二,在所有的數據分析過程中,最好能養成創建副本的習慣,這樣才能毫無顧慮的來進行處理。
2.4.1 數據的可視化
數據可視化主要是指藉助於圖形化的手段以清晰有效的傳達數據信息的一類方法。數據可視化的目的是幫助人更好的分析數據,因為數據信息的質量很大程度上依賴於其表達方式,純數字化的信息雖然最真實、最原始,但是從數字中概括提取出的圖形表達更加直觀,更加靈活。在可視化的分析下,將數據的多維特徵進行分類、排序、組合和顯示,這樣就可以看到數據不同屬性的關係特點。以本章加利福尼亞州房屋價格數據集為例,是利用地理位置信息(經度和緯度)來繪製所有數據的散點圖來對數據進行可視化。
加州房屋價格散點圖(HMLST figure 2-13)
如圖(HMLST figure 2-13)所示,其中每一個散點表示一個地區(即一條數據),散點半徑大小表示人口聚集程度,顏色不同對應房屋價格不同(從藍到紅對應價格從低到高)。我們就可以從圖中很直觀的觀測到,哪些區域的數據點比較密集,哪些區域的房屋價格比較高,又有哪些區域數據點比較分散,房屋價格比較低。比如,沿海地區的數據點就分布非常密集,價格相對較高,東邊遠海地區就相對分布稀疏,價格較低。還有人口密度,人口密度大的區域價格明顯高於人口稀少的地區。所以,我們可以認為是否靠近海域和人口密度與房屋價格有很大的關係,而我們生活中也通常認為這種聯繫是肯定存在的。而這種關係應該怎樣確定,可以大膽假想一下,我們利用聚類演算法來找出聚類中心及測量聚類中心的接近度,當然如果完全沒有接觸過聚類這一概念也沒有關係,因為也並沒有真的要做下去,之所以提到這個,是希望我們在學習各種演算法以後,能夠在實際解決各類問題的時候盡量選擇比較合適的演算法進行嘗試。
在可視化的過程中,如果數據特徵較多,一步到位是不太可能的,第一次生成的圖形不夠直觀反映出數據特點是極有可能的,以上圖為例,在一開始生成地理位置散點圖的時候並沒有像上圖那麼分布明顯,基本上都是連成一片的,為什麼會這樣呢。這是因為很多地區的經緯度很接近,在圖中就會堆積在一起,難以觀測。此時一般通過調節可視化參數來調節圖形顯示效果。本例中通過調節透明度(alpha=0.1)來讓散點分布顯示的更加明顯。
2.4.2 數據屬性的相關性
在數據處理過程中,通常採用計算標準相關係數(standard correlation coefficient,反映兩變數間線性相關關係的統計指標)的方法來量化各個屬性之間的相關性。由於本例中的數據集不算太大,可以直接使用 corr( )函數來計算每一對屬性之間的標準相關係數。由於本例的目標是預測房屋的價格,所以就主要分析其他屬性與房屋價格之間的相關性。結果如下所示。
標準相關係數的範圍一般為-1到1。當它接近1時,說明兩屬性之間表現出很強的正相關性;當係數接近-1時,表示兩屬性之間表現出很強的負相關性。比如,本例中與房屋價格相關係數最接近1的是收入中位數(median_income),這說明當收入中位數上升時,房屋價格中位數也往往會增加。而最接近-1的是緯度(latitude),但是相關係數絕對值較小,所以負相關性也不是很強。如果觀察仔細的話,也能觀測到圖中越往北(緯度上升)價格有輕微下降的趨勢。最後,當標準相關係數接近零時,意味著兩個屬性之間幾乎不存在線性相關性。注意,這裡只能說它們不存在線性相關關係,並不能排除它們存在其他相關關係。下圖顯示了各種數據集橫軸變數與縱軸變數之間的標準相關係數。
各種數據集的標準相關係數(HMLST figure 2-14)
如圖(HMLST figure 2-14)所示,標準相關係數僅能表達兩個變數之間的線性相關程度,並不能反映出變數之間有無非線性關係。
仔細觀察上圖中最後一行的圖形,即使它們的相關係數都等於零,但是它們的橫縱軸變數並不相互獨立。 所以標準相關係數只能用來判定是否線性相關,而不能判斷變數之間是否相互獨立。另外一種檢驗數據屬性之間的相關性的方法是直接使用Pandas的scatter_matrix函數。該函數的功能是將每個數字屬性與所有其他的屬性一一對應繪製散點圖,本例中有11個數字屬性,所以一共可以生成121個圖形,如HMLST figure 2-15所示(部分)。其中每個屬性與自身組合會生成該屬性的直方圖,在這裡,這些圖不是很有用,可以不考慮它們。但是即使忽略掉這些直方圖,所保留的圖形還是太多了,將這麼多圖形都放在一起觀察當然不合適,所以從中挑選出與我們的目標屬性(即房屋價格中值)相關性最高的屬性散點圖進行進一步的分析。比如將選擇出來的圖進行放大,調節圖形參數,使其更加可視化,然後分析圖形得到一些數據屬性之間的關係特點,比如上升趨勢,分布特點,數值上限等等。
散點圖矩陣(HMLST figure 2-15)
2.4.3 數據屬性的組合經過前面的工作,已經對獲取的原始數據集的各個屬性有了很多的了解,並且可能考察了各屬性之間的相關性,特別是對各個屬性與目標屬性的相關性進行分析,對完成後續工作尤其重要。接下來,我們應該考慮如何利用那些與目標屬性並不是很相關的數據屬性。這個地方可能會有疑問,既然它們不是很相關,為什麼不直接拋棄這些數據屬性。因為這些屬性也屬於數據的自身特徵,既然我們是對數據所有的特徵進行深入的挖掘,所以也要儘可能地用上所有的數據。於是我們嘗試對各種屬性進行組合,將那些與目標屬性不是很相關的屬性組合在一起,組合成新的屬性,考慮它們與目標屬性的相關性,如果相關性比那些單獨屬性的相關性強,我們就可以利用這些組合屬性來對目標屬性進行預測。比如,如果你不知道本例中某個地區有多少戶,則該地區的房間總數就不是很有用,因為更想獲取的是每一戶的房間數量。同樣,單獨的卧室總量不是很有用,如果將它與房間總數進行比較,就可能得到更加有用的屬性。還有每戶人口看起來也是一個不錯的組合。我們可以對看起來比較合適的屬性進行組合,然後接著就分析它們與目標屬性的相關性。
從這個結果可以看出,屬性組合的嘗試確實取得了比單獨屬性更強的相關性,而且取得的效果非常明顯。例如,新的屬性bedrooms_per_room(所有房間中卧室的比例)與房屋價格屬性的相關性比房間總數或卧室總數強的多得多,而rooms_per_household(每戶的房間數)表現出的相關性與房屋總數和住戶總數上的相關性相比也有所提升。
這樣一個通過屬性組合的方式來獲取更合適屬性的方法實際上是一個試驗的過程,既然是試驗,那麼避免不了反覆嘗試。在這裡,可以假設已經建立一個模型,並運行得到了輸出,但是結果不甚理想,那麼除了其他的改進方法,也可以考慮回到這一步再進行嘗試。
2.5 為演算法準備數據
到這一步,對原始數據的分析應該差不多了。接下來,我們就應該為模型和演算法準備輸入數據了。一般情況下,原始數據因為它的一些特點和缺陷是不能直接輸入到模型里的,需要對它們進行一些操作使得它更加規範,更加結構化。當然這個過程,是不可能僅僅依靠人工操作來完成的,我們可以考慮使用轉換函數來完成這項工作。這樣做的好處主要有:
1) 可以很輕鬆地在任何數據集上重現這些轉換操作。
2) 通過不同操作地實現,可以逐步建立可重複使用地轉換函數庫。
3) 也可以在實時系統中使用這些函數對實時獲取的新數據進行轉換。
4) 可以輕鬆地嘗試各種轉換,以便獲取最好的轉換組合。
這裡值得注意的是我們要回到原始地訓練集上進行操作,而且要把預測變數和標籤拿開,
因為我們不一定要對預測變數和目標值進行相同地轉換。
2.5.1 數據清理
數據清理是對一些有特徵值遺漏的數據進行處理,因為大多數的機器學習演算法都不能處理具有屬性缺失的數據,所以我們可以針對這種數據構建函數來補上這些遺漏。如果仔細觀察原始數據集會發現total_bedrooms這個屬性值的部分缺失。一般有三種選擇來解決這個問題:
? 將這些屬性缺失的數據樣本直接清理掉 。
? 將有缺失值的屬性直接去除。
? 將這些缺失的值直接設置為某個值(零,平均值,中位數等)。
在本例中,我們可以直接調用DataFrame』s dropna(),
drop(), fillna()這三個函數來輕鬆實現這三種選擇。如下:
如果選擇第三種方法,一般會將缺失值設置為中位數,即計算訓練集所有該屬性值的中值並用它來填充那些缺失的值,計算的這個中值最好也能保存下來,因為可能在最後的測試階段也需要這個值來替換測試集中的缺失值。很方便的是Scikit-Learn已經提供了一個類(Imputer)來專門做這項工作。如何使用這個類,可以參考下列程序。還有就是因為無法知道系統啟用後新數據中是否存在缺失值,所以將Imputer應用於所有的數據屬性會更加安全。
這種用中位數填充缺失值的方法只適用數值型的數據屬性,於是我們首先要建立一個沒有文字屬性(如ocean_proximity)的副本再進行數據清理,然後將Imputer類作用於訓練集的所有數據。
這裡簡單介紹一下Scikit框架的設計。Scikit-Learn的API(應用程序界面)設計的非常好。它的設計優點主要表現在:一致性,所有對象共享一個操作簡單的界面,其中主要包括評估、轉換和預測三個部分。可查性,所有評估器的超參數都可以通過公共實例變數直接訪問,學習參數也可以通過帶有下劃線後綴的公共實例變數進行訪問,所以可以很方便的對它們進行檢查。不擴散類,數據集在使用Scikit框架時通常表示為NumPy數組或SciPy稀疏矩陣,而不是自定義的類,其中的超參數也只是用字元串和數字表示的。結構統一,盡量重複使用現有的模塊,比如,在任意的轉換器和最終的評估器之間創建一個流水線評估過程。合理的默認設置,Scikit-Learn為大多數參數設置了非常合理的默認值,這樣可以快速建立基礎的工作系統。
2.5.2 文本分類屬性
在上一步數據清理的過程中我們忽略了分類屬性ocean_proximity,因為它屬於文本屬性不能計算它的中值,而絕大多數的機器學習演算法都喜歡使用數值型數據。所以考慮將屬性的分類標籤轉換為數字表示,比如我們可以用0,1,2,3來分別表示這些類別。Scikit-Learn提供了一個LabelEncoder函數來完成這項工作。
可能考慮到這樣一個問題,機器學習演算法會假定兩個接近的值更具有相似性,但是實際情況並非如此。為了解決這個問題,Scikit-Learn提供了一個OneHotEncoder編碼器來將整數分類值轉換為一個熱點向量,即如果該樣本屬於某類,則將該類對應的元素值設為1,其他值設為0。另外,使用大量內存來存儲零是非常浪費的,所以可以使用稀疏矩陣只存儲非零元素的位置,可以使用LabelBinarizer函數來獲得這個稀疏矩陣。
2.5.3 自定義轉換
雖然Scikit-Learn自身提供了許多非常有用的轉換功能,但是學會編寫自己的轉換函數來針對一些特定的諸如自定義清理或組合屬性等任務,是非常有必要的。當然我們希望自定義的轉換器能夠與Scikit的轉換器無縫協作,又由於Scikit-Learn並不依賴繼承輸入,所以自定義轉換器只需要自定義一個類並實現三個功能:fit()(返回self),transform()和fit_transform()。其中只需將TransformerMixin添加為基類,就可以擁有第三個功能。
2.5.4 特徵縮放
特徵縮放就是標準化數據特徵的範圍,從而使得每個特徵的範圍有可比性,比如將取值範圍處理為0到1之間。因為在收集原始數據時往往存在不同屬性值之間數字比例不同的情況,比如:房間總數分布從6到39320,而收入中值範圍僅為0到15,比例相差巨大。將這樣的數據作為機器學習演算法輸入通常效果不佳。所以進行特徵縮放是非常有必要的。常用的特徵縮放方法包括:線性縮放,標準化。
線性縮放(也叫線性歸一化)非常簡單,它是將數據的特徵值縮放到[0,1]之間。首先取數據樣本的最大值和最小值,然後用每條數據值減去最小值除以最大值與最小值的差,公式如下。可以使用Scikit-Learn提供的MinMaxScaler函數來直接實現線性縮放。
標準化方法是將原始數據集歸一化為均值為0方差為1的數據集,公式如下。與線性縮放不同的是標準化方法不將值限制在特定的範圍內,只是要求數據集縮放後滿足一定的特點,這與具體的演算法要求有關。標準化方法的優點在於受異常值的影響較小。Scikit-Learn提供了一個StandardScaler函數實現標準化縮放。
2.5.4 轉換流水線
前面有講到許多轉換步驟,而這些步驟需要按照正確的順序來執行,從而形成一個合理的轉換流水線。這裡我們直接用Scikit-Learn包含Pipeline類來幫助完成這個工作。
Pipline首先構造一個利用函數名稱/評估器對的列表來定義一系列的轉換步驟。其中除了最後一個評估器,其他的評估器都必須具有轉換功能。然後當你調用Pipline的fit()時,它就會在所有轉換器上依次調用fit_transform(),並且將每次調用的輸出作為參數傳遞到下一個調用的轉換器,直到流水線工作完成。
上面構造了一個數值型的轉換流水線,還需要將文本類的分類屬性也加入到流水線中,Scikit-Learn提供了用於分類值的解析器,然後提供了一個FeatureUnion類用於將分類屬性的轉換加入到流水線當中。
2.6 選擇並訓練模型
經過上述的一系列操作,我們已經得到了一批可以用於訓練機器學習演算法的數據。那麼下面,我們將正式選擇一些機器學習演算法,並對其進行訓練。
2.6.1 對訓練集的訓練和評估
正如我們在2.1.2節分析的那樣,本次案例的問題是一個有監督的多元回歸問題,處理時將採用離線學習的方式。在本節中,我們將用已處理好的數據來分別訓練線性回歸和決策樹回歸兩個模型,並通過對比最終結果來選出更適用於解決本案例的演算法。
線性回歸(LinearRegression)
>>from sklearn.linear_model import LinearRegression
>>lin_reg = LinearRegression()
>>lin_reg.fit(housing_prepared, housing_labels)
先用所有訓練數據對當前演算法進行訓練,並將訓練好的模型用於預測訓練集的部分樣例進行預測,並將得到的預測結果跟樣本標籤作對比,我們得到了以下結果:
顯而易見,線性回歸模型在本次預測中的表現並不好,第二個樣本預測的點誤差甚至達到了50%,為了更加定量地衡量該演算法的表現,我們將引入mean_squared_error函數來計算該回歸模型的均方根誤差:
>> from sklearn.metrics import mean_squared_error
>> housing_predictions = lin_reg.predict(housing_prepared)
>> lin_mse = mean_squared_error(housing_labels, housing_predictions)
通過計算可得,該模型的均方根誤差為68628.413。顯然,對於一個目標值基本處於120000到265000的數據集來說,68628的誤差是巨大的。故我們可以簡單推測,線性回歸模型學得的特徵並不能為預測提供足夠的有效信息,該模型在擬合該訓練集時存在欠擬合現象。解決欠擬合現象的主要方式是換一種更強的模型,在這裡,我們將嘗試決策樹回歸模型。
決策樹回歸(DecisionTreeRegressor)
>>from sklearn.tree import DecisionTreeRegressor
>>tree_reg = DecisionTreeRegressor()
>>tree_reg.fit(housing_prepared, housing_labels)
決策樹是一種可以學習出數據集中複雜的非線性關係的回歸模型,我們將使用同一批數據訓練決策樹,並同樣用mean_squared_error函數計算其均方根誤差,以下是運行結果:
>> housing_predictions = tree_reg.predict(housing_prepared)
>> tree_mse = mean_squared_error(housing_labels, housing_predictions)
>> tree_rmse = np.sqrt(tree_mse)
>> tree_rmse
0.0
這很像顏文字是嗎,我看到這個結果的表情也是這樣的,居然沒有誤差!看起來很完美的結果,但事實確實是這樣嗎?要知道,這是基於訓練集數據得到的結果,當一個模型完美擬合訓練集數據時,它在測試集上的表現通常會令人失望,這種現象被稱為過擬合。為了驗證模型是否出現過擬合現象,我們通常將訓練集的數據再次劃分,一部分用於模型訓練,另一部分用於模型驗證。這樣才能保證當模型用於測試集能有更好的效果。
2.6.2 交叉驗證法
交叉驗證法又名k折交叉驗證法,即將當前測試集劃分為k個大小相似,分部相似的互斥子集,以其中k-1個子集為訓練集,並以剩下的子集為驗證集,從而進行k次訓練和驗證,並將k次運算誤差的均值作為最終判定結果。下圖來源於周志華老師的《機器學習》[1],展示了10折交叉驗證演算法的運算過程。
下面我們將引入cross_val_score函數來驗證上一節所訓練的決策樹模型,並附上運行結果:
>>from sklearn.model_selection import cross_val_score
>>scores = cross_val_score(tree_reg, housing_prepared, housing_labels,
>>scoring="neg_mean_squared_error", cv=10)
>>rmse_scores = np.sqrt(-scores)
驗證結果:
上圖中,Scores向量中存儲的是十次驗證得到的均方根誤差, Standard deviation是這十個數值的標準差,而Mean是十次誤差的均值,是該交叉驗證函數的返回值。經過這次交叉驗證,我們得出一個結論:該模型在預測新數據時會有產生均值約為71200,標準差約為3200的均方根誤差,這甚至比線性回歸模型的效果還差。這驗證了上節中對於該模型過擬合數據集的猜測。
為了公平起見,我們將對線性回歸模型也實行交叉驗證:
結果表明,決策樹模型確實存在過擬合現象,而過擬合對預測新數據產生的壞影響甚至超過模型欠擬合。
鑒於線性回歸模型和決策樹都不太適用於該數據集,我們將嘗試另一種新的模型:隨機森林(RandomForestRegression)。隨機森林是集成學習的一種,集成學習即通過構建並結合多個學習器來完成學習任務。隨機森林以決策樹為基學習器,在訓練學習期的過程中引入隨機屬性選擇,最終以所有決策樹輸出的平均值作為隨機森林的輸出。
>> from sklearn.ensemble import RandomForestRegressor
>> forest_reg = RandomForestRegressor()
>> forest_reg.fit(housing_prepared, housing_labels)
運行結果:
>> forest_rmse
22542.396440343684
交叉驗證結果:
無論是在整個測試集下的運算結果,還是交叉驗證的結果都表明隨機森林比前兩種模型的運行效果好。但是當對比兩次運行結果時,交叉驗證的誤差與測試集誤差依然差距巨大,這表明,該模型中依然存在過擬合現象。解決過擬合的方法通常是簡化模型,添加正則化項,或增大測試集規模。
可以用來解決這個房價預測問題的演算法有很多,這裡嘗試了線性回歸、決策樹和隨機森林。除此之外,諸如神經網路、支持向量機等其他機器學習演算法也可以使用,這些都將在這本書的後面幾章詳細講解。在選定某個模型之前,我們總是要做很多嘗試,在嘗試完一系列演算法後,選擇其中的三到五個作為待選演算法,並通過調參進一步選擇。
2.7 模型調試
在講解調參過程之前,我們首先先來關注模型參數與模型超參數的區別。模型參數是模型內部的配置變數,可以用數據估計模型參數的值;模型超參數是模型外部的配置,必須手動設置參數的值。我們常說的調參,正是調節超參數,即嘗試不同的超參數組合以期找到其中使模型效果達到最好的一種。如果這個過程由手動完成,會增加不必要的時間花銷,故一些調參演算法應運而生。
2.7.1 網格搜索(GridSearchCV)
>>from sklearn.model_selection import GridSearchCV
當調用調參演算法時,我們只需要告訴這個演算法該模型中需要調試的參數以及參數的取值範圍,該演算法會嘗試所有的超參數組合,並使用交叉驗證法驗證結果,最終輸出最優的一組參數。例如,在調節隨機森林的參數時:
>>param_grid = [ {n_estimators: [3, 10, 30], max_features: [2, 4, 6, 8]},
{bootstrap: [False], n_estimators: [3, 10], max_features: [2, 3, 4]},]
>>forest_reg = RandomForestRegressor()
>>grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
>>scoring=neg_mean_squared_error)
>>grid_search.fit(housing_prepared, housing_labels)
param_grid指出了需要調試的超參數及參數取值範圍,當超參數』bootstrap』=True時,嘗試3*4=12種『n_estimators』和』max_features』的組合,而當』bootstrap』=False時嘗試另外2*3=6種組合。也就是說,在本次參數搜尋的過程中,網格搜索演算法將作出12+6=18種不同的參數組合,以期得到其中使隨機森林的表現達到最好的超參數組合。Grid_search()函數中的參數cv代表k折交叉驗證的k值,那麼對於整個訓練過程,模型將被訓練18*5=90次。其運行結果如下:
>> grid_search.best_params_
{max_features: 6, n_estimators: 30}
當max_feature=6,n_estimators=30時,隨機森林的均方根誤差為49959,而未調參之前的均方根誤差是52634。
除此之外,超參數還可以指數據處理的一些階段。例如特徵選擇,離群值處理,特徵損失處理等等都可以用網格搜索來自動尋找到最佳解決方案。
2.7.2 隨機搜索(RandomizedSearchCV)
通過使用網格搜索,我們已經確定了調參對模型性能的提高有一定幫助。但是在網格搜索中,超參數的搜索空間是較小的(上例中僅嘗試了18組參數組合),當超參數的搜索空間巨大時,隨機搜索(RandomizedSearchCV)更具有優勢。隨機搜索的原理與網格搜索基本相似,在每次迭代過程中隨機選取參數的隨機值作隨機組合進行嘗試,隨機搜索有以下兩種優勢:
1、當隨機搜索運行1000次迭代,則此方法將為每個超參數探查1000個不同的值(而不是網格搜索中每個超參數限定的幾個值)。
2、只需設置迭代次數,就可以較好地控制要分配給超參數搜索的計算量分配。
2.7.3 集成方法
除了調節模型超參數,調試模型還有一種更為簡單粗暴的方式,即將弱學習器組合起來,形成一個新的強學習器。集成後的學習器通常比組合中表現最好的獨立學習器的效果更好(例如隨機森林的學習能力通常強於單棵決策樹),這種優勢尤其體現在一些具有不同的弱點的學習器集成成新模型的時候。
2.7.4 分析最優模型及其誤差
正如在2.7.2節最後提到的,參數搜索可以應用於多個方面,我們可以從多方面去優化模型,並取得其中最佳的解決方案。而在這個過程中,我們也可以檢測出特徵間的內部聯繫,例如,隨機森林可以預測出每個屬性對於準確預測的相對重要性:
通過上圖給出的信息,我們可以選擇剔除一些相關性較小的特徵,以減少計算量。同時,我們還可以關注系統產生的具體錯誤,然後試著理解它為什麼會產生這些錯誤,以及思考可以用什麼方法去減少錯誤(例如增加或減少一些特徵,清理異常值等)。
2.7.5 在測試集上評估模型
經過模型調整,我們基本已經得到一個可以使用的,表現良好的模型。現在,是時候在測試集上真正測定模型了。我們將用求得的模型對測試集的樣本點進行預測,並將預測的結果與樣本的標籤做對比,依然通過計算均方根誤差評估模型的優劣:
>>final_rmse = np.sqrt(final_mse) # => evaluates to 48,209.6
模型在測試集上的預測效果通常比用交叉驗證法驗證出的結果稍微差一點,因為交叉驗證是在驗證機上測試的,在測試集上確認的超參數並不能再未知數據集上有同樣的好效果,這是正常現象。你或許很想試著調試一下超參數,使模型在測試集上有更好的表現。請打消這一想法,因為這會影響模型在新數據上的泛化能力。
下面是項目啟動前的最後階段,你需要做以下幾步:列出針對問題的假設和系統的限制條件,記錄之前學習的所有模型以及它們的成績,記錄所有內容,總結出一些清晰明了的結論(例如,平均收入是房價的頭號預測指標)。
2.8 啟動,監控和維護系統
現在,我們已經完成了模型的建立部分,並且已經取得了不錯的實驗性能,那麼接下來這個機器學習系統就可以上線了。但是在系統啟動之前我們應該思考一下,首先,我們不肯考慮到了所有的情況,可能會有很多極端情況出現。因此我們需要設置很多報警機制,為一些意外情況進行備案。此外,這個系統不會只使用一次,也不會短時間內就被捨棄,那麼如何讓系統更有生命力?怎樣讓系統與時俱進呢?
我們知道,無論我們有多少訓練數據和測試數據,我們都不能保證系統運行後處理的數據和訓練數據是完全獨立同分布的,因為在你獲得訓練數據的那一刻它可能就已經「過時」了。所以,為了使系統能夠不斷的更新和適應數據分布的變化,我們可以考慮為我們的模型引入在線學習(Online Learning)機制。筆者曾涉獵過在線學習領域,私以為並不是所有的演算法都能改進出在線版本。如此一來,我們要另尋他法,例如依據一個周期內接收到的新數據對模型進行更新,也就是定期的維護系統。
2. 9 總結
在第二章我們用房價預測這個例子熟悉了一個完整的項目流程,以上是作者按照他的"Machine Learning Project Checklist "來完成這個項目的步驟,我們可以依據具體的任務和自己的特點來構建自己的清單。同時,我們學完這一章可以發現其實作者並沒有將大部分時間放在選模型和調參上面,而是在跑演算法之前仔細的分析了數據的特點,我認為這是十分重要的。筆者在剛接觸機器學習的時候很冒進,拿到數據就想直接上演算法,以為只要演算法高明、參數設置的好就可以不管數據本身所隱藏的信息,結果往往事倍功半,最後還要回頭分析數據做特徵。希望大家能夠立足數據本身,踏實的分析數據,這樣對後面的演算法選擇和調參都會有很多指導。
這是專欄的第二篇文章,我們的成員能力有限,水平不足,但還是希望能與大家一起討論學習。另外,我們在不斷的規範寫作,以後會做到定期更新,對那些期待我們文章的讀者表示感謝,同時對於文章的延期深感抱歉。
——Eva Dan, waY, Double_D 編輯
參考文獻:
[1] 周志華. 機器學習[M]. Qing hua da xue chu ban she, 2016.完整代碼:
ageron/handson-ml推薦閱讀:
※[PRML學習筆記] CHA 4 Linear Models for Classification
※機器學習入門:邏輯回歸案例
※Learning Explanatory Rules from Noisy Data 閱讀筆記2
※第一章:機器學習在能源互聯網中的應用綜述(一)
※機器學習入門之泰坦尼克號案例
TAG:機器學習 | 深度學習DeepLearning | 數據挖掘 |