《機器學習實戰(基於scikit-learn和TensorFlow)》第二章內容的學習心得
請支援正版圖書, 購買連結
下方內容裡面很多連結需要我們科學上網,請大家自備梯子,實在不會再請留言,節約彼此時間。
當開始著手進行一個端到端的機器學習專案,大致需要以下幾個步驟:
- 觀察大局
- 分析業務,確定工作方向與效能指標
- 獲得資料
- 藉助框架分析資料
- 機器學習演算法的資料準備
- 選擇和訓練模型
- 微調模型
- 展示解決方案
- 啟動、監控和維護系統
接下來,我將對每一個部分自己的心得進行總結。
一、觀察大局
當開始一個真實機器學習專案時,需要針對專案的特點,有針對性進行分析。任何專案都有其最終的目的,總的說來,我覺得首先要考慮的是 業務需求,即用這個模型要解決什麼事情,需要的程度如何等等的問題 。 假設專案是一個預測的問題,那麼我需要知道的是我上游環節能給我什麼樣的資料,下游的環節需要我提供怎樣的資料流,是具體的價格(要精確到何種地步?)?還是一個等級劃分?這些都決定了自己在這個專案上所花費的精力以及使用的模型、測量方法等一系列問題,總之要求不一樣,進行的處理就不一樣,這就是大局觀。
二、分析業務,確定工作方向與效能指標
因為我們是要進行機器學習專案,所以應該根據業務對我們的專案要構造的機器學習系統的種類進行劃分。從長遠來看,機器學習系統大致有以下幾種:
- 是否在人類監督下訓練:監督式學習、無監督學習、半監督學習與強化學習
- 是否可以動態地進行增量學習:線上學習和批量學習
- 是簡單地將新的資料點和已知的資料點進行匹配,還是對訓練的資料進行有針對性的模型預測:基於例項的學習與基於模型的學習
這裡,以文中的”房價預測“為例。
1、分析機器學習系統的類別
這裡可以先提前給出部分資料,如下圖。讓我們來思考一下,這個預測問題應該劃分為什麼類別。
我們可以看到,圖中資料有很多屬性,由於我們最終做的是房價預測而資料中你會發現有一列屬性很耀眼”median_house_value“,這個屬性是該地區的房價中位數屬性,那我就明白了一件事,那就是我要預測的結果應該與該地區的房價中位數差不多才是正確的預測結果,所以我的機器學習系統應該是一個 有監督的學習系統,是一個多變量回歸的問題。
2、測試指標的選擇
因為已經確定了是一個對變數的迴歸問題,因此測試指標就應該對應多變量回歸問題進行。
迴歸問題的典型效能衡量指標首選是均方根誤差(RMSE),這裡首先給出它的公式供大家參考:
注:其中的m是資料集例項中的例項數量, x 是資料集中所有例項的所有特徵值的矩陣,h(x)為系統的預測函式,即我們要預測的結果,y為真實值。
上式的意思就是描述預測值與真實值之間的差異,RMSE的值越小,說明預測效果越好,預測值與真實值的偏差越小。
( 這裡附一個 連結 ,關於講解均方根誤差、標準差、方差之間的關係。總的說來,標準差是資料序列與均值的關係,而均方根誤差是資料序列與真實值之間的關係。因此,標準差是用來衡量一組數自身的離散程度,而均方根誤差是用來衡量觀測值同真值之間的偏差,它們的研究物件和研究目的不同,但是計算過程類似 )
如果獲得的資料中存在許多的離群區域,我們可以考慮用平均絕對誤差(MAE),這裡給出公式:
這個公式可以很好的解決偏離抵消的情況,真實反映出預測值與真實值的偏差,當然MAE的值越小越好。
上述兩個公式相比,我們可以發現,RMSE對異常值更加敏感,因為當出現一個較大的偏差的異常值時,會極大影響結果的數值,MAE則影響相對小。
所以, RMSE更加適合資料分佈為鐘形的曲線(類似正態分佈)的情況,而MAE更加適合資料偏離散的情況 。
三、獲得資料
我的建議是要獲取真實的資料進行操作。書中列出了很多的開源資料:
- 流行的開放資料儲存庫:
— UC Irvine Machine Learning Repository
- 源門戶站點(他們會列出很多開放的資料庫):
- 其他的一些許多流行的開放資料庫頁面:
— Wikipedia’s list of Machine Learning datasets
這裡開放的資料許多是國外資料庫,訪問需要科學上網,如急切需要但不知如何科學上網,請與我聯絡(nfuquan AT gmail.com)
以後會更新更多的關於我們中文的資料庫,盡情期待。
四、藉助框架分析資料
用Anaconda元件中的Jupyter Notebook說明,程式語言為python3.5。
1、Pandas
具體它的功能就不去介紹了,可以附一個 連結 ,自行檢視。
這裡集中說明幾個函式的功能,方便以後自己能快速檢視。
- pandas.DataFrames.head()
這個函式是讀取DataFrames格式資料,預設讀取5個元素,當然可以在引數中傳遞自己想讀取的函式的個數。
- pandas.DataFrames.info()
這個函式是列出DataFrames格式中的索引資訊、行資訊、非空值資訊以及記憶體使用資訊,這個函式可以清楚地告訴我們資料的異常狀態,從而讓我們在數值上進行處理。
- pandas.DataFrames.describe()
這個函式顯示資料的各個屬性摘要,同時針對數值資料進行了資料的一些計算。
- pandas.DataFrames[“屬性”].value_counts()
這個函式是對屬性中的內容進行統計。
- pandas.DataFrames.hist()
首先應該在首行加入%matplotlib inline (該式只能在jupyter notebook中使用,意思是直接編譯顯示結果)。
這個函式是將資料每個屬性製作為一個直方圖。有很多的引數,具體請檢視 連結
上述函式給我們一個極大的便利就是 針對資料的具體情況可以進行逐個分析 。例如,我們可以看到收入中位數“median_income”最大值為15,這個不符合常識,收入因該至少在五位數才對,因此需要針對該資料對資料提供方進行詢問,經詢問,得知該屬性進行了屬性的縮放。 在直方圖中,我們一般應該注意的方面是資料的範圍以及數量、分佈 ,為什麼要關注這些呢,因為要結合實際。舉個例子,比如我們發現房屋價格的中位數最大到50萬美元,而且數量很多,而且根據資料的分佈,感覺存在異常,如果對資料敏感的話,我們應該有這樣的疑問:是否這個資料是一個限制條件,就是說將這個資料以上的資料全部都設定為50萬,這會導致我們的學習演算法永遠不會估計出超過50萬元的數值,這個結果是否可接受,需要考慮。如果要求是可以預測出50萬元以上的房屋價格,我們可以針對50萬以上的房屋價格重新收集資料或者除去50萬元以上的資料,同時允許我們的機器學習演算法預測出50萬元以上的結果。直方圖還表現了一個特徵是有幾個屬性值分佈不均勻,有可能會影響機器學習系統難以預測的某些模式。
書中介紹了一個典型偏誤“資料窺探偏誤”,這裡先解釋一下:人腦容易過度匹配,當發現某些資料模式有特徵時,可能就會只訓練某些有特徵的資料,而這些資料很有可能不代表普遍規則,如果機器學習演算法採用這些資料做訓練,就會導致結果可能很好,但是在預測實際內容時,會缺乏泛化能力。我們需要科學的資料劃分!
資料集劃分:訓練集(訓練+驗證)、測試集
假設資料量較大5000-10000內,可以用隨機選擇的方式,按照訓練80%,測試20%進行分佈。假設資料量在100000以上,可以考慮將訓練集的比例增加,測試集比例降低。如果資料很少,則隨機選擇資料劃分可能存在問題,要按照一定規則分佈的規律中進行抽樣選擇。以房屋價格預測為例,預測的價格與人們的收入中位數屬性密切相關,因此我們應該以收入中位數的屬性特徵進行樣本的劃分:
上圖是收入中位數資料,我們可以看出,收入在2-4人數比較多,同時看出基本上在收入上是一個連續的分佈,不能進行一個分層的抽樣,因此,我們應該將上述資料進行一個劃分,分成5類或者6類(這個分類可以自己定)。
操作步驟:1、使用numpy框架的ceil()函式進行取整操作
2、然後使用where()函式,將取整後的類別值大於5的類別統一歸為類別
上述操作我們獲得了一個分類標籤屬性“income_cat”,注意這個標籤只是為了區別各個資料的收入中位數類別,因此這個標籤可以在使用完畢之後進行刪除。
使用sklearn框架中的Stratified-Shuffle Split類可以進行分層抽樣。
上面的語句的意思是首先對需要分層的類別進行一個預設定,然後通過一個for迴圈按照資料中的“income_cat”屬性中的分層情況進行劃分,得出分佈一樣訓練集和測試集。具體的類別怎麼操作,請查閱 連結
使用pandas.DataFrame中的drop()可以將函式中的某個屬性進行刪除,因此我們在使用完“income_cat”後在測試集中與訓練集中將該屬性刪除:
至此,我們按照科學的方式對資料進行了劃分,使得測試集與訓練集的分佈一致,這是一個合理科學的劃分方式。
當我們將測試集與訓練集劃分好後,測試集不宜改動,我們應該重點關注訓練集,並從中獲取更多的易於構建我們機器學習系統的資訊。
訓練集的探索:
首先,建立一個訓練集副本!任何探索都不能改變原始資料集合,應該建立副本,在副本上進行探索!使用copy()函式進行拷貝。
還是迴歸到資料集的屬性上,“longitiude”與“latitude”是經緯度,這個資料是不會變的,相比其他的資料來說,這個資料在顯示上更容易,因此我們可以對該資料進行視覺化操作。
plot()函式是繪圖函式,具體的介紹,請看 連結
在上述函式中有個屬性是alpha,改變該值,可以提高顯示影象的疏密程度,這對於我們的資料探索有幫助,因此:
可以看出,沿海的房屋資料更多,內陸的房屋資料較少些,沿海地區中也存在集中現象。
在此基礎上,我們將房價資訊新增到上圖中。
使用jet的一個預定義的顏色表表示房屋價值,每個園的半徑顯示出該地區人口數量:
從這張圖,資訊量就大多了。首先,我們可以觀察到,越靠近沿海地區( 再解釋一下,該資料是加利福尼亞州的房屋價格資料,因此根據美國地理位置,沿海地區一目瞭然 )房屋價格更高,同時人口的分佈大致呈兩條弧線,沿海地區與內陸地區的人口分佈在總體上大致保持一致。
因為資料的量不大,我們可以讓計算機對資料的相關性進行一個計算。
使用corr()函式,計算各屬性與房價中位數的關係。
相關性的值,越靠近1說明正相關性越強,越靠近-1說明負相關性越強,越靠近0說明兩者之間沒有相關性。注意,這裡corr()計算的是一種線性相關關係,不是非線性相關關係。
從上圖,房價中位數與收入中位數有很強的相關性。
使用Pandas中的scatter_matrix()函式可以繪製各屬性之間的相關性,如果不加限制條件,函式將把所有屬性與其他屬性的相關關係全部計算。我們的資料中存在11個屬性,因此如果不加限制條件,會繪製11*11=121個圖形。選取與房價中位數直覺上最相關的屬性計算,這裡選取“median_income”、“total_rooms”、“housing_median_age”三個屬性計算相關性:
這張圖的對角線需要說明一下,因為相同的屬性的相關性肯定是1,因此顯示直方圖。
我們重點關注一下“income_value”與“house_median_value”的關係:
從圖中可以看出,在50萬有一條橫線,在35萬、45萬處好像也存在直線,我們在繼續的資料處理中應該對這些點進行去除,防止系統重現這些怪異資料。
除了上述針對原屬性的介紹,我們其實也可以針對屬性進行組合,形成新屬性。
我們從之前給出的屬性,我們可以進行分析,這裡再給出我們的屬性:
在屬性中,我們能觀察到“total_bedrooms”、“total_rooms”、“population”、“households”可能存在一定聯絡,那麼是否可以對這幾個屬性進行一個數據融合形成一個新屬性呢?當然可以,並且有助於深刻探索資料。書中針對該方面新創新了3個屬性:
上述屬性含義分別是:平均每房屋的房間數、平均房間中的臥室數、平均人口持有房屋數
我們可以通過計算相關性,看看新屬性對“house_median_value”的關係:
“bedrooms_per_room”與房屋中位數屬性相關度比其他大部分屬性高,說明新屬性的創造是有效的。
五、機器學習演算法的資料準備
針對給機器學習演算法的資料準備,我認為有以下幾個方面:
- 不完全資料的合理補全
- 非數值屬性的變換
- 新增新屬性,刪除相關性差的屬性
- 資料數值的合理化(歸一化、均值化等)
從技術的角度,我們應該採用一套自動化的資料轉換,而不是每次都手動轉換。原因有以下幾點:
- 可以在任意資料集上重現轉換(比如:獲得更新的資料庫之後)
- 逐漸建立起一個轉換函式的函式庫,可重用
- 在實時系統中使用這些函式來轉換新資料再給演算法
- 可嘗試多種轉換系統,檢視哪個轉換組合最佳
首先,同樣建立一個去除預測值的訓練集(X_Train),將預測值傳入新的list中(Y_train)。
準備工作完畢後,正式開始:
不完全資料的合理補全:
從之前的info()函式,我們已經得出有些資料是不全的,比如“total_bedrooms”。有三種辦法對該屬性值進行處理:
- 放棄缺失的地區
- 放棄該屬性
- 將缺失值設定為某一個合理值
由於這個屬性缺失數量比較小,因此打算採用第三種方式進行。
sklearn中的Imputer類可以處理該問題,首先應建立該類例項。由於這個類處理時需要純數值的資料,因此我們要預先將非數值的資料進行刪除後再做處理:
Imputer.fit()是計算各屬性的策略值,並儲存在imputer.statistics_中。這樣做的好處就是我們不知道未來的資料中哪個部分存在缺失,我們可以針對這些缺失做出處理。
然後開始替換:
這個imputer例項將把housing_num中的缺失值替換為之前計算好的數值。轉換後的X是一個numpy框架定義的陣列,如果想轉換為pandas的DataFrames,可以進行如下操作:
文字資料的處理:
“ocean_proximity”是一個文字屬性,要變為數值屬性,演算法才能更好的工作。
sklearn中的LabelEncoder是實現這個功能的類。
廢話不多說,直接上程式碼,馬上就懂:
從結果可以發現,這個函式的功能是將文字集合對映為數字的過程。這個對映為:
內容和序號實現了對映,0對應‘1H OCEAN’,以此類推。
這裡,我們需要考慮一個問題:對映的方式是沒有錯的,但是畢竟是數值,數值就存在大小,從數值上說1就是比4小,因此這個差異可能會導致學習演算法的精準度,因此採用one-hot編碼更好些!如果不曉得,請點此 連結 查詢。
sklearn類提供了一個OneHotEncoder類來提供此服務。
先上程式碼,然後解釋:
reshape()函式是將numpy陣列的形狀進行轉換,這裡的-1需要解釋一下: 一個引數為-1時,那麼reshape函式會根據另一個引數的維度計算出陣列的另外一個shape屬性值。
fit_transform() 函式是將 fit()函式和transform() 函式結合的函式,它的作用是先將資料做規則的操作,然後再對資料轉換為標準形式。
housing_cat_1hot將會轉變為一個scipy的矩陣,是一個稀疏的矩陣。當然如果需要轉換為numpy型別的矩陣,只需要做如下操作:
toarray()函式可以轉為numpy型別的陣列。
LabelBinarizer類可以將上述兩個步驟合併,同時通過傳送sparse_output=True給LabelBinarizer類的建構函式可以獲得稀疏矩陣。
自定義轉換器:
這個步驟的意義在於,使用框架中的方法總不能很好的契合我們的實際需要,我們可以利用框架中的方法,通過自己定義轉換器,將框架中的方法最有效的利用到我們的轉換器中,這樣可以節省很多時間!
先上程式碼,再分析:
我們首先要明白一個概念,然後再來解釋為什麼要這樣做,這個概念是鴨子型別,不懂的小夥伴請點選 連結 看一下含義。
這個類是組合屬性新增類,引數是基本估計器和轉換估計器,這裡給個 連結 ,需要的朋友們去連結裡面看一下這兩個東西是啥。首先我們知道資料屬性的順序,因此可以直接預定義需要查詢到每個屬性的索引序號,room_ix,bedroom_ix等都是其索引序號。然後是__init__()函式,這個是建構函式,用來初始化某個需要的屬性標識,預設為Ture(也就是預設是要新增的!)。定義fit()函式,將引數self、X(外部傳遞的引數)傳遞給它,同時返回的self是一個具有fit()函式處理過的結果。設定transform()函式,同樣將引數傳遞給它。這個函式就會根據我們在建構函式中傳入的引數add_bedrooms_per_room的true或者False來判斷是否需要新增這個屬性,如果不新增,我們就只會新增rooms_per_household與population_per_household兩個屬性。np.c_函式 是按行連線兩個矩陣,就是把兩矩陣左右相加,要求行數相等。至此,類的設定就完畢了。
CombineAttributesAdder()這個就是初始化我們的類,並且給這個類提供引數,如果不填則預設需要新增add_bedrooms_per_room這個屬性。最後,使用類中transform()函式,將屬性新增進來得到一個新的添加了新屬性的矩陣。
特徵縮放:
如果資料大小存在非常明顯的差異,那麼有可能會導致機器學習演算法效能不佳。同比例的縮放所有屬性有兩種常用的方法:最大最小縮放和標準化。
- 最大最小縮放
將值重新縮放使其最終範圍歸於0到1之間,實現方法是將值減去最小值併除以最大值和最小值的差。我們可以用sklearn中的MinMaxScaler的轉換器,當然不想範圍是0-1可以通過傳遞超引數feature_range進行更改。
- 標準化
首先減去平均值,除以方差,使得結果的分佈具有單位方差。可以使用standadScaler()函式。
標準化並不會將結果繫結到一個範圍中,有可能不適合某些對輸入資料有要求的處理,但是它相比上一個縮放方式,受到異常值的影響更小,比如假設某地區平均收入是1000(這是一個錯誤資料),縮放就會將所有資料從0-15降到0-0.015,影響較大,但如果是標準化,異常值不會干擾其他正常資料。
具體使用見下面部分!
轉換流水線:
sklearn有一個很好的思想就是將轉換操作工廠流水線化,它有一個非常nice的類,叫做Pipeline類,這個類可以幫我們實現對資料集處理的自動化操作!
Pipeline類的建構函式會通過一系列的名稱/估算器的配對來定義步驟序列。先上程式碼:
這一步可能會有很多童鞋報錯,有的會說缺少sklearn_features的包,有的會報引數個數的錯誤,這裡副個連結,有問題請點選這個 連結 。
除了匯入了需要的包之外,我們首先看到定義了兩個引數,一個是list型別的數值文字屬性名稱( ),一個是類別引數,因為這裡只有“ocean_proximity”這個屬性是文字屬性,因此我們只將這個屬性賦值給cat_attribs中。
首先,要先自定義一下這個DataFrameSelector,先給出程式碼:
這裡給出的就是按照引數給出的數值屬性進行轉換。
接下來開始定義Pipeline,我們只需要在構造方法中傳入我們需要的順序即可。比如num_pipeline,在建構函式中傳入我們需要執行的函式 DataFrameSelector() 、Imputer()、CombineAttributesAdders()、StandardScaler(),同樣方法可對其他的流水線進行了定義。
每一條子流水線都是從選擇器轉換器開始,然後挑出所需屬性(數值或者分類),生成的DataFrame再轉換為numpy陣列就可以進行訓練了。
至此,針對演算法的資料準備基本到此告一段落。
六、選擇和訓練模型
在這個階段,應該做的就是選擇一個合適的模型,訓練該模型,並評估預測的質量了。
首先可以嘗試選擇一個線性迴歸模型。
程式碼怎麼用,這裡不解釋了,看程式碼就知道了。這裡通過fit()函式後,lin_reg就是我們通過訓練集得出的第一個訓練模型,接下來可以通過predict來進行預測。我們可以通過訓練集的一些資料進行:
我們從總資料中選出五個資料進行測試,雖然測試結果不盡如人意,但是系統可以工作了,這個還是很讓人興奮的!
那麼,怎麼評估演算法的效能呢?我們使用RMSE。
從之前的資料中,大多地區的房價中位數在12萬美元到26萬美元之間,我們的預測基本與之相差6萬8的誤差,說明我們的系統對資料擬合存在嚴重不足。當這種情況發生時,可能是由於特徵資訊不能提供足夠的資訊讓我們做出更好的預測還有就是演算法不夠強大。因此到了這一步,你就有兩種選擇,一個是需要更多新屬性來支援演算法,還有一個就是選擇更強大的模型或者減少模型約束(當然,我們選擇的線性模型沒有什麼約束,談不上這一點…)。
我們用更強大的演算法試試!
用決策樹模型,試試效果:
我擦,RMSE最後的結果竟然為0?!完美的演算法?of course not! 很大程度上,不存在完美的演算法,應該是資料嚴重過擬合了。。。
用K折交叉驗證的方式,能夠還原演算法的本來面貌。
交叉驗證,說白了就是將訓練集科學分成K份,每次選擇1份做驗證集,其他為訓練集,分別訓練K次,選出效能最好的模型作為最後的模型:
這裡從cross_val_score的引數中可以發現,第一個引數是模型,第二個引數是訓練集,第三個引數是標籤,第四個引數是選擇打分型別,第五個引數是選擇那個K,最後得出的score就是一個K大小的陣列,每個裡面放著一次的得分。
從結果上看,平均得分在69549,上下浮動2000左右。
我們再看看線性迴歸模型:
線性迴歸跑分是69088,比決策樹稍差。
我們最後再看看隨機森林模型。
隨機森林模型顯然不如決策樹模型。
我們嘗試了很多種不同的模型,原因是在這個階段我們需要嘗試不同模型,選出其中有潛力的模型,千萬不要花太多時間去調整每個模型的超引數,確定要使用的模型才是這個階段最關鍵的!
至於模型的過擬合問題或者其他什麼問題,我們等確定模型後再除錯即可。
每個模型都應該妥善的儲存,這裡介紹一個python中的pickel模組或者sklearn.externals的joblib模組:
七、微調模型
假設,你現在已經確定了好幾個有潛力的模型,那麼這時候需要微調它們了。
微調的一個方法是對模型的超引數進行調整,這個過程很耗時,你需要科學的工具幫助你。使用sklearn中的GridSearchCV來幫助你。
這裡面param_grid首先評估第一個dict中的n_estimator和max_features的所有可能的組合(3*4=12),然後嘗試第二個dict中的引數組合(2*3=6)。最後需要嘗試6+12=18種組合,並對每個模型進行5次訓練。當執行完畢後,可獲得最佳引數組合:
同時,我可以得到最好的估算器:
評估分數也可以得出:
對了,有些資料準備也可以當作超引數來處理,比如之前的自定義轉換器中是否新增“add_bedrooms_per_room”。
當然若超引數數量非常大,一般選擇RandomizedSearchCV,這個函式會在每次迭代中為每個超引數隨機選擇一個值,對一定數量的迭代進行評估,這裡不詳細介紹了,具體請Google。
檢查最佳模型,我們還可以得出每個屬性的相對重要程度:
說明一下最下面那個array[]是之前的ocean_proximity屬性。
好了,我們微調過後,通過測試集評估系統吧。
這個結果比之前就好多了!
八、展示解決方案
這裡可以通過製作PPT等方式,強調系統學習到了什麼、什麼屬性是有用的、演算法基於了什麼假設、系統目前存在的限制有什麼,用清晰視覺化的方式呈現!
九、啟動、監控和維護系統
這裡就是即將讓專案進入實戰的環節,這時候應該為生產環境做準備,特別是將生產資料接入系統,當然為了防止出錯,我們必須要進行程式碼測試!
監控程式碼也要編寫,這是為了定期檢查系統的實時效能,在效能下降後觸發警報!
任何模型幾乎都經不起時間的演化,隨著時間的演化,適合現階段的引數可能不是該模型存在的引數,模型會漸漸“腐化”,所以我們應該定期使用新的資料訓練模型,讓模型保持年輕。同時做好模型備份,防止最新迭代的模型出現錯誤好馬上回滾。
還要注意的是,模型的預測結果需要專家進行分析,確保環節安全。
最後要注意的是要監控輸入系統的資料的質量,及時查找出質量較差的資料,防止汙染系統。
好啦,我的心得在這裡就算總結完畢了。這個總結對我來說,我又重新認識了一遍專案的流程,這個對我收益很大,以後要多多堅持自己的總結,加油呀!
圖中程式碼可通過以下方式獲得:
2、百度雲分享: https://pan.baidu.com/s/1xjBGBFbM7hqp-tUO3oNEtA (sio0)
轉載請註明出處,謝謝!
有任何問題,請在下方留言,博主看到會進行回覆。