0. 前言
- “盡管新技術新算法層出不窮,但是掌握好基礎算法就能解決手頭 90% 的機器學習問題。”
-
1.任務描述
- 預測任務:根據某時刻房價相關數據,預測區域內該時刻任一街區的平均房價,決定是否對投資該街區的房子。
- 評估函數:RMSE
評估函數:RMSE即損失函數,范數越大,越注重預測值誤差大的樣例
- 工具:ipython notebook
2.數據概覽
整體概況
用 python 的pandas包導入樣本數據,查看前五行,每一行表示一個街區,包含十項數據特征:
經度 | 維度 | 房齡 | 房間數 | 卧室數 | 人口數 | 家庭數 | 收入 | 房價 | 臨海 |
---|---|---|---|---|---|---|---|---|---|
-122.23 | 37.88 | 41 | 880 | 129 | 322 | 126 | 8.3252 | 452600 | NEAR BAY |
-122.22 | 37.86 | 21 | 7099 | 1106 | 2401 | 1138 | 8.3014 | 358500 | NEAR BAY |
-122.24 | 37.85 | 52 | 1467 | 190 | 496 | 177 | 7.2574 | 352100 | NEAR BAY |
-122.25 | 37.85 | 52 | 1274 | 235 | 558 | 219 | 5.6431 | 341300 | NEAR BAY |
-122.25 | 37.85 | 52 | 1627 | 280 | 565 | 259 | 3.8462 | 342200 | NEAR BAY |
其中房價一列實際是此次任務的 label, 查看數據基本情況:
RangeIndex: 20640 entries, 0 to 20639
Data columns (total 10 columns):
longitude 20640 non-null float64
latitude 20640 non-null float64
housing_median_age 20640 non-null float64
total_rooms 20640 non-null float64
total_bedrooms 20433 non-null float64
population 20640 non-null float64
households 20640 non-null float64
median_income 20640 non-null float64
median_house_value 20640 non-null float64
ocean_proximity 20640 non-null object
dtypes: float64(9), object(1)
可以看到一共有 20640 行記錄,注意到其中 total_rooms 屬性有207行缺失,ocean_proximity 屬性是 object 類型,查看該屬性的統計情況:
<1H OCEAN 9136
INLAND 6551
NEAR OCEAN 2658
NEAR BAY 2290
ISLAND 5
對9項數值型屬性畫直方圖,感受下每個屬性的分布情況:
可以看到 housing_median_age、median_house_value、median_income 有明顯的翹尾情況,說明這三個屬性在數據收集階段,做過被截斷處理。median_house_value 作為此次任務的預測目標值,它的截斷處理在實際情況中可能是個問題,因為它意味着你模型的預測值可能無法超過這個上限。上圖還可以看出有四項屬性屬於“長尾分布”的情況,一般來說,機器學習的模型更喜歡類似正態分布的“鍾型”特征。
可視化
人腦非常擅長圖像視覺的信息處理。用經度緯度確定地理位置,用圓圈大小表示人口數量,用熱力圖表示房價高低,再從外部導入一張經緯度吻合的地圖,可以得到一張可視化的數據圖:
可以看到海景房房價普遍偏高,但是北部區域是個例外,說明臨海距離應該是個很好的特征。另外,人口聚集地中心區域,房價也相應偏高,是不是做聚類之后,計算到聚類中心的距離也是一個很好的特征呢?
相關性計算
相關性既包括特征與label的相關性,也包括特征之間的相關性。首先看特征與 label 之間的皮爾遜系數:
median_income 0.687160
total_rooms 0.135097
housing_median_age 0.114110
households 0.064506
total_bedrooms 0.047689
population -0.026920
longitude -0.047432
latitude -0.142724
觀察房價與收入的二維圖:
總體上,房價與收入具有很強的相關性,收入越高,該地區的房價也越高。但是也有一些收入較高,但是房價很低,或者收入較低,房價奇高的點,可能是異常點,需要從訓練數據中去除。
3. 數據准備
隨機采樣
測試集從數據集中分離而來,大小一般設置為全部數據的20%。分離的辦法可以隨機取,有點是采樣均勻,缺點是每次取出的測試集可能不一樣。要克服這個缺點,在隨機取的時候,可以固定隨機數種子。其他的方法還有根據某列屬性的尾號或者 md5 來划分,優點是每次運行的測試集是固定的,確定是要確保尾號或者 md5 數值是均勻分布的,並且與 label 不存在相關性。在工業界,上線算法ab分桶時,常常也用類似的方法,比如按照用戶id尾號分流、按照用戶id的md5分流等,特別要注意未登錄用戶的分桶情況,因為未登錄用戶與點擊率是有相關性的。
分組按比例隨機采樣
隨機產生測試集,可能有的一個弊端是,重要屬性的部分區間可能丟失。舉個極端的例子,在所有分類模型中,label是最重要的屬性,假設100個樣本中只有2個正樣本,則有很大概率訓練樣本全部是負樣本。這個任務中,median_income 屬性是決定房價的一個重要特征,在直方圖中,可以看到收入大於7.5的樣本已經比較稀疏了。如果完全按照隨機采樣方法產生訓練樣本和測試樣本,有可能訓練樣本中完全不包含收入大於7.5的樣本,這對於模型訓練是很不利的。
解決的方法很簡單,將 median_income 進行分組,每組里面按照0.2的比例抽取測試樣本。分組的時候,注意每組里面樣本數量不能太少,可以參考如下處理方法:
group = ceil(min(median\_income,7.5) / 1.5)
將 median_income 分為5組,每組按比例采樣,這樣處理的結果是,測試樣本在 median_income 的分布,比當做整體(一組)完全隨機采樣的方法,更符合整體分布。
數據預處理
- 空值處理:數據清洗包括,total_rooms 用中位數代替,ocean_proximity 用one-hot-encode編碼轉為數值型,one-hot-encode與直接編碼為 [0,1,2,3..] 相比,不會引入相似或者相近的意義。比如 2 和 3 在數值上相近,但是它們各自表示的NEAR BAY與INLAND屬性項並不存在實質的相似關系(盡管有這個可能)。
- 構造特征:數據集里因為不同district里戶數不同,所以 total_rooms、total_bedrooms、population 這些屬性可能沒有太大的意義,而每戶的房間數、卧室數、人數相關性則較,所有在數據集中可以新增這些特征。
- 歸一化:數值型特征的一個特點是,不同的屬性取值范圍差別極大,對模型學習、正則項設置不利。有兩種簡單的方法可以應對這種情況:變換到0-1區間的歸一化或者標准正態分布。前者對異常點非常敏感,如果存在某個值比正常值區間距離很遠,這種處理會導致正常數據點分布過於集中;后者對過於集中的數據分布敏感,如果原數據集方差很小,除以近0的數值容易導致精度失真。
- 偏度處理:在大部分數值型特征中,通常分布不符合正態分布,而是類似於“長尾”。例如房價中,低價占大部分,豪宅屬於小部分。應對這種數據分布,一般可以通過神奇的log化處理轉化類近似正態分布。
4. 模型訓練
訓練集和測試集
先用線性回歸模型在訓練集上訓練,然后用訓練好的模型再次預測訓練集的label,計算RMSE為6.8w刀,MAE誤差為4.9w刀,模型明顯underfitting。解決的方法有:用復雜模型、用更好的特征、用更弱的約束。
改用決策樹模型進行訓練,得到訓練集的誤差為0.0,在測試集中誤差極大 。在回歸問題里,百分百完全預測准確基本是不可能的,說明模型存在過擬合現象。解決的方法有:用簡單模型、用更大的數據量、更強的約束、ensemble方法等。為了更好評估過擬合情況,可以用 K-fold 進行交叉驗證。基本過程如下:把全部數據集隨機分成K份,每次挑選其中K-1份訓練數據,剩下一份作為測試集計算誤差,總共重復K次。相比於只划分一份訓練集和測試集,這種方法更加健壯。
隨機森林模型是多個決策樹模型ensemble后的結果,比單個決策樹模型具有更強抗overfitting的能力。改用隨機森林模型進行訓練,發現結果好得多,但是測試集誤差依舊遠遠大於訓練集誤差,說明模型還需要更強的約束。為了找到較好的約束參數,可以采用grid search方法。
grid search
模型的超參可能有多個,每個有多種選擇,由此形成了參數網格grid。前面講過用交叉驗證的方法可以評估一個模型的效果,以上兩種方法結合,可以讓模型遍歷每個網格點,用交叉驗證的方法得到該網格點的模型預測誤差,從而找到表現最好的模型。最終找的的較好超參是 max_features 為6,n_estimators 為 30, RMSE 49900。
5. kaggle實戰
准確率top4%的 kenerl 地址鏈接,真實的數據集和本文以上描述有所區別
數據處理
- outliers直接用邊界值去除,注意去除過程也是也風險的。
- 分析target分布,發現有偏斜,用log轉化為近似正態分布
特征工程
- 空值處理。空值的處理方法要因地制宜,主要有:轉換為某個固定字符串如“None”或者用0代替;以用近鄰記錄該特征的中位數代替;通過其他特征推斷;用數據集中最常出現的值代替;部分可以移除等
- 類型轉換。數值型特征可能表示category信息,統一轉化為str類型。
- 編碼。所有類別特征都轉化為one hot encode,對於可能包含順序關系的,額外添加label encode編碼。
- 添加特征。在現有特征上,變換添加可能對預測目標有強關聯的特征。
- Box-Cox變換。對偏度大於0.75的數值型特征進行Box-Cox變換(類似於log),轉換為近正態分布。
模型訓練
- 先后應用LASSO Regression、Elastic Net Regression、Kernel Ridge Regression、Gradient Boosting Regression、XGBoost、LightGBM基礎模型,注意部分模型對scale較敏感,應先經過RobustScaler處理。
Stacking models
- 最簡單的方法,以上模型預測值取平均,可以得到比單個模型好的預測效果。
- 次簡單的方法,因為不同模型精度不一樣,理應取不同的權重,上面直接取平均太粗暴,改用 regression 來訓練權重。所以訓練集還需要划分出一部分數據,用來訓練lasso regression模型。
Ensembling
- 用 ElasticNet、KernelRidge、 GradientBoostingRegressor 做底層模型,用 lasso regression 來得到每個模型的權重,Stacking models RMSE loss:0.07815
- 用 XGBRegressor 單模型得分RMSE loss:0.07851
- 用 LGBMRegressor 單模型得分RMSE loss: 0.07194
- 最后拍個權重,求平均:stacked_pred*0.70 + xgb_pred*0.15 + lgb_pred*0.15 得到最終預測RMSE loss: 0.07524