Kaggle競賽入門實戰——機器學習預測房屋價格

這篇文章是介紹一個完整的機器學習小項目——預測房屋價格,它是Kaggle競賽中入門級的題目,和我們比較熟悉的泰坦尼克號生存預測處於同一等級。在之前介紹KNN算法時,曾用過這個數據集,但只是通過簡單的建模幫助理解KNN的思想,本文會更加全面地介紹完成一個小項目的流程,如何在科學分析的輔助下預測出我們需要的目標值。

在分析之前我們應該提前明確我們的目的,中途可能需要處理的問題,可以歸納成以下幾點:

  • 瞭解標籤變量:可以通過目標變量大致分析出解決問題是需要分類算法還是迴歸算法。
  • 粗略瞭解特徵:因爲特徵標籤都爲英文,所以理解標籤含義是最基本的,觀察每個特徵的特點,是數值型的、還是字符型的;是離散的、還是連續的;粗略聯想一下各個特徵與目標變量間的聯繫。
  • 數據預處理:針對性處理數據集中的缺失數據和異常數據。
  • 研究主要特徵:數據集中可能只有一部分特徵與目標變量間相關性極強,重點分析這些特徵與目標變量間的聯繫。
  • 選擇性處理其他特徵:除主要特徵外,可能主觀上認爲某些特徵也會影響目標變量,也可以選擇性分析一下。
  • 建模工作:選出最適合該問題的模型,進行建模、調參等操作。

由於每個人的習慣不同,所以處理問題時的先後順序、選擇的方法自然也不同,比如說處理缺失值的方法就有很多種,本文提及的流程、方法只是個人的一點小思路,希望給夥伴們參考,但絕不侷限於此,不論算法還是模型不都應該向更優處發展嘛。

在這裏插入圖片描述

首先一定要大致瞭解一下測試數據集,上圖只是數據集的一小部分,共80個特徵+一個目標變量“SalePrice”,共有1460個樣本。很明顯這個問題是需要通過房子的一些特徵預測出相應的價格,所以建模可以選擇迴歸類算法。

缺失值處理

這個數據集許多特徵都或多或少的含有一些缺失值,針對缺失值個數的多少,採取的處理方式也不同,像一些特徵的缺失值佔樣本個數超過三分之二,那麼這些特徵的意義也不大,所以選擇捨去這些特徵:

# 很多特徵還不及數據個數的三分之一,所以選擇捨去
data.drop(columns = ['Alley','FireplaceQu','PoolQC','Fence','MiscFeature'],axis = 1,inplace = True)

而對於缺失值較少的數值型特徵,根據情況可以選擇填充衆數、中位數和平均數:

#填補數值型缺失值
data['LotFrontage'].fillna(data['LotFrontage'].median(),inplace = True)
data['MasVnrArea'].fillna(data['MasVnrArea'].mean(),inplace = True)

而數據集中本身含有很多字符型特徵,而對於這類特徵的缺失值,是沒有中位數和平均數一說的,可以隨機填充或者填充出現頻率最多的元素,這裏我選擇了後者:

#獲得含有缺失值的字符型特徵標籤
miss_index_list = data.isnull().any()[data.isnull().any().values == True].index.tolist()
miss_list = [] #存元素
for i in miss_index_list: #注意需要reshape規格
    miss_list.append(data[i].values.reshape(-1,1))

這裏填充缺失值的方式可以用上述方式,但前提是需要另寫一個函數計算特徵的衆數,另一種方式就是利用sklearn中自帶的API進行填充:

from sklearn.impute import SimpleImputer
#用衆數填補數值型變量
for i in range(len(miss_list)):
    imp_most = SimpleImputer(strategy='most_frequent') #實例化,參數選擇衆數
    imp_most = imp_most.fit_transform(miss_list[i]) #訓練
    data.loc[:,miss_index_list[i]] = imp_most #替換原來的一列

當然這種方式也適用於數值型,其中參數strategy也是可選的,像均值、中位數、衆數和自定義這幾種,代碼中most_frequent就代表衆數。對於填充缺失值的方式,numpy和pandas應用較多,但這種利用API填充也比較便利,可以當成一次拓展,自己瞭解一下。

處理字符型特徵

對於某些迴歸類算法,比如線性迴歸,是通過計算繼而預測出最後的目標變量,所以訓練時傳入字符型元素是不合法的。但如果利用隨機森林可以避免,因爲由決策樹構成的隨機森林只注重樣本特徵的分佈,但現在我們並不知道哪一種模型更適合該問題,因爲最後我們要從中挑選出一個最優的,那麼我們在處理數據時就要兼顧我們將要選擇的所有模型。

這裏選擇利用啞變量(也稱獨熱編碼)處理字符型數據,可能有的人比較陌生,但介於篇幅問題不在過多闡述,可以自行查詢一下,或者過段時間也會寫一篇文章單獨講一下變量處理的相關知識。

我們只針對字符型特徵進行啞變量轉化,所以需要索引出字符型類的特徵:

data_ = data.copy() #在一個新的數據集上操作
ob_features = data_.select_dtypes(include=['object']).columns.tolist()

然後就可以通過sklearn中自帶的API對字符型特徵進行啞變量轉換:

#啞變量/獨熱編碼
from sklearn.preprocessing import OneHotEncoder
OneHot = OneHotEncoder(categories='auto')#實例化
result = OneHot.fit_transform(data_.loc[:,ob_features]).toarray()#訓練

打印一下result輸出如下:

在這裏插入圖片描述

這個矩陣中的每一列可以看成一個新的特徵,而每個特徵只包含0和1兩個元素,其中1代表有、0代表沒有。比如Street特徵中包含兩個元素Grvl和Pave,而這兩個元素就可以通過啞變量轉化形成兩個新的特徵。Street特徵中爲Grvl的樣本在Gral新一列中就爲1,在Pave新一列中就爲0,其他特徵也是如此。

#獲取特徵名
OneHotnames = OneHot.get_feature_names().tolist()
OneHotDf = pd.DataFrame(result,columns=OneHotnames)

利用get_feature_names方法可以獲取特徵的標籤,將上述矩陣轉化爲一個DataFrame:
在這裏插入圖片描述

過濾方差

因爲轉化爲啞變量之後許多特徵都是由0和1組成的,仍然用Street舉例,會不會有種可能就是所有樣本的Street中的元素都爲Grvl呢?如果這樣啞變量轉化後的Grvl一列皆爲1,而Pave一列皆爲0,那麼這兩個新的特徵對於最後的目標變量的預測是沒有一點用處的,是可以直接刪去的,所以利用方差過濾掉類似的情況,將閾值設爲0.1,方差低於0.1的特徵都將被過濾掉。

from sklearn.feature_selection import VarianceThreshold
#過濾掉方差小於0.1的特徵
transfer = VarianceThreshold(threshold=0.1)
new_data1 = transfer.fit_transform(data_)
#get_support得到的需要留下的下標索引
var_index = transfer.get_support(True).tolist()
data1 = data_.iloc[:,var_index]

最後利用該方法過濾掉了188個特徵,原本的270個特徵還剩下82個特徵。

相關性過濾

82個特徵仍然太多了,一定還有一些無關緊要的特徵,需要繼續特徵降維,如果一個特徵對於目標變量的預測有幫助,那麼這個特徵與目標變量間一定有某種聯繫,那麼這個特徵與目標變量之間的相關性一定是比較高的,所以可以通過特徵與目標變量間的相關係數過濾一些特徵。

#皮爾遜相關係數
from scipy.stats import pearsonr
pear_num = [] #存係數 
pear_name = [] #存特徵名稱
feature_names = data1.columns.tolist()
#得到每個特徵與SalePrice間的相關係數
for i in range(0,len(feature_names)-1):
    print('%s和%s之間的皮爾遜相關係數爲%f'%(feature_names[i],feature_names[-1],pearsonr(data1[feature_names[i]],data1[feature_names[-1]])[0]))
    if (abs(pearsonr(data1[feature_names[i]],data1[feature_names[-1]])[0])>0.5):
        pear_num.append(pearsonr(data1[feature_names[i]],data1[feature_names[-1]])[0])
        pear_name.append(feature_names[i])

我們設定閾值爲0.5,留下相關係數絕對值大於0.5的特徵,因爲不止正相關有影響,負相關也是對預測有一定作用的。

在這裏插入圖片描述

經過濾後只剩下13個特徵,上述計算的是特徵與目標變量之間的相關性,我們可以再從特徵與特徵之間的相關性找一找聯繫,可不可以再降幾維呢?

在這裏插入圖片描述

  • TotalBsmtSF(房間總數)和GrLivArea(地面以上居住面積)相關係數0.83;
  • GarageCars(車庫可裝車輛個數)和GarageArea(車庫面積)相關係數0.88;
  • TotalBsmtSF(地下室總面積)和1stFlrSF(第一層面積)相關係數0.82;

個人覺得可以將TotalBsmtSF和GrLivArea都保留,因爲房間面積和房間總數不會過於衝突,可能在房間面積相同的情況下,房間個數越多的房子價格會更高一些呢?而剩下的四個特徵從意思上就有一些衝突,可以取GarageArea和otalBsmtSF兩個特徵。

可視化分析

我們可以通過可視化驗證一下過濾後的特徵與目標變量之間是否存在上文我們預想的某種聯繫,這裏推薦用箱線圖繪製數據較爲離散的特徵、用散點圖繪製數據較爲連續的特徵,前者方便比較,後者方便觀察分佈,利於找出聯繫。
在這裏插入圖片描述
在這裏插入圖片描述
可以看到OverallQual(總體評價)對於價格的影響真的巨大,所以買賣口碑真的是非常重要呀,其他離散特徵與目標變量間的聯繫也是具有很明顯的趨勢的。
在這裏插入圖片描述
散點圖可視化出的結果也是非常明顯,所以我們選取的這幾個特徵都是不錯的,但是這不代表其他特徵與目標變量之間沒有聯繫,之前也提及過可以選取一些主觀上感覺有聯繫的特徵再進行二次分析。我自己猜測Neighborhood(地理位置)和HeatingQC(加熱質量)對於價格應該會有影響,但是可視化出的結果顯示聯繫並不大。

選模、調參

模型測試我選擇用線性迴歸和隨機森林,由於線性迴歸對傳入數據的要求以及要通過均方誤差判斷哪一個模型更優,所以需要先對訓練集和測試集進行標準化,然後計算兩個模型對應的均方誤差,代碼如下:

#計算模型的均方誤差
clfs = {'rfr':RandomForestRegressor(),
      'LR':LinearRegression()}
for clf in clfs:
        clfs[clf].fit(x_train_sta,y_train_sta)
        prediction = clfs[clf].predict(x_test_sta)
        print(clf + " RMSE:" + str(np.sqrt(metrics.mean_squared_error(y_test_sta,prediction))))
'''
rfr RMSE:0.38292937665826626
LR RMSE:0.5534982168680332
'''     

在不調整參數的情況下,rfr的均方誤差小於LR,隨機森林在該問題上是要優於線性迴歸的,所以選定隨機森林作爲最後建模要用的算法,然後利用網格搜索或者學習曲線對算法進行調參。

這裏省略很多很多調參步驟。

最後我自己嘗試調出的最佳參數如下,調參之後的模型得分大約爲84%,前後相比大約提高了大約2%。

rf = RandomForestRegressor(n_estimators=300,max_depth =20,
                          max_features =5,min_samples_leaf =2,
                          min_samples_split=2)
grid = GridSearchCV(rf,param_grid=param_grid,cv = 5)
grid.fit(x_train,y_train)
rf_reg = grid.best_estimator_#最佳模型

可以在模型的基礎上分析一下特徵重要程度,其中OverallQual(總體評價)和GrLivArea(居住面積)兩者重要程度佔比就已經超過五成,反觀x32_Unf(未完成地下室面積)和x29_TA(廚房質量)影響甚微。

在這裏插入圖片描述

需要對測試集做與訓練集一致的操作,例如缺失值處理、啞變量轉化等,然後索引出測試集中這些重要特徵並傳入建好的模型中,得出最終的預測結果"SalePrice",然後將"Id"和"SalePrice"導出一個名爲submission的csv文件,最後需要在Kaggle需要上傳這份文件,就可以得到自己的排名啦。

submission_Id = pd.read_csv("house_test.csv",usecols=['Id'])
SalePrice = pd.DataFrame(test_value_y,columns=['SalePrice'])
Submission = pd.concat([submission_Id,SalePrice],axis=1)
Submission.to_csv("submission.csv",index = False)

總結

綜上可以發現完成一個小項目五成的時間用來處理分析數據、四成的時間用來調參、最後一成時間用來選模建模,而最終模型的好壞五成取決數據本身、四成取決於你的特徵工程、最後一成取決於你的調參,所以需要把更多時間更多精力放在數據處理、特徵工程上才能得到讓自己滿意的一個結果。

公衆號【奶糖貓】回覆"房價預測"可獲取源碼供參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章