广州二手房价分析与预测
一、概述
?
1.1問題介紹與分析
隨著社會經濟的迅猛發展,房地產開發建設的速度越來越快,二手房市場迅猛發展,對二手房房產價格評估的需求也隨之增大.因此,對二手房房價預測與分析是必要的.詳細文檔與代碼資料保存在我的百度云盤。
數據收集:在鏈家網站上收集廣州二手房數據,把數據分為訓練集、測試集。訓練和測試數據是一些二手房信息以及價格狀況,要嘗試根據它生成合適的模型并預測其他二手房的價格狀況。這是一個回歸問題,很多回歸算法都可以解決。
收集工具:八爪魚
?
?
1.2總體思路
首先從數據清洗開始,對缺失值的多維度處理、對離群點的剔除方法以及對字符、空格等的處理;其次進行特征工程,包括類別特征編碼、連續特征編碼等;再次進行特征選擇,采用了xgboost,xgboost的訓練過程即對特征重要性的排序過程;最后一部分是模型設計與分析,采用了嶺回歸模型、xgboost,取得了不錯的效果,此外還介紹了模型融合方法。
?
二、 數據清洗
?
2.1缺失值的多維度處理
(1)通常遇到缺值的情況,我們會有幾種常見的處理方式
①如果缺值的樣本占總數比例極高,我們可能就直接舍棄了,作為特征加入的話,可能反倒帶入noise,影響最后的結果了
②如果缺值的樣本適中,而該屬性非連續值特征屬性(比如說類目屬性),那就把NaN作為一個新類別,加到類別特征中
③如果缺值的樣本適中,而該屬性為連續值特征屬性,有時候我們會考慮給定一個step(比如這里的age,我們可以考慮每隔2/3歲為一個步長),然后把它離散化,之后把NaN作為一個type加到屬性類目中。
(2)有些情況下,缺失的值個數并不是特別多,那我們也可以試著根據已有的值,擬合一下數據,補充上。
2.2數據預處理
#載入數據:
train=pd.read_csv('train.csv')
test= pd.read_csv('test.csv')
#檢視源數據
print(train.head())
?
#合并數據
這么做主要是為了用DF進行數據預處理的時候更加方便。等所有的需要的預處理進行完之后,我們再把他們分隔開。
首先,價格作為我們的訓練目標,只會出現在訓練集中,不會在測試集中。所以,我們先把價格這一列給拿出來。
?
#可視化價格特征
prices= pd.DataFrame({"price":train["價格"],"log(price + 1)":np.log1p(train["價格"])})
prices.hist()
#可見,label本身并不平滑。為了分類器的學習更加準確,首先把label給“平滑化”(正態化)
#這里使用log1p, 也就是 log(x+1),避免了復值的問題。值得注意的是,如果這里把數據都給平滑化了,那么最后算結果的時候,要把預測到的平滑數據給變回去。按照“怎么來的怎么去”原則,log1p()就需要expm1(); 同理,log()就需要exp()
?
y_train= np.log1p(train.pop('價格'))
#然后把剩下的部分合并起來?????
all_df= pd.concat((train, test), axis=0)
?
三、特征工程
?
#變量轉換
all_df["樓齡"]=2017-all_df["樓齡"]
#有一些數據是缺失的,根據缺失值的多少進行相應的處理
#查看缺失情況
print(all_df.isnull().sum().sort_values(ascending=False).head(10))
#可以看到,缺失最多的column是裝修情況。由于裝修情況的值缺失太多就刪掉這一列
all_df.pop("裝修情況")
?
#接著處理樓齡的缺失值,由于缺失不多,這里我用平均值進行填充
mean_col= all_df["樓齡"].mean()
all_df["樓齡"]=all_df["樓齡"].fillna(mean_col)
?
#由于變量交易權屬 建筑類型 配備電梯的絕大多數的值相同,區分的效果不會很明顯這里就刪掉了
all_df.pop("建筑類型")
all_df.pop("配備電梯")
all_df.pop("交易權屬")
#該特征很多都不相同,這里簡單處理就刪掉了
all_df.pop("房型")
?
#看看是不是沒有空缺了?
print(all_df.isnull().sum().sum())
?
#把category的變量轉變成numerical表達形式
#當我們用numerical來表達categorical的時候,要注意,數字本身有大小的含義所以亂用數字會給之后的模型學習帶來麻煩。于是我們可以用One-Hot的方法來表達category。pandas自帶的get_dummies方法可以幫你一鍵做到One-Hot。
pd.get_dummies(all['區域'], prefix='區域')
#此刻區域被分成了5個column,每一個代表一個category。是就是1,不是就是0。
#同理,我們把所有的category數據,都給One-Hot了
all_dummy_df= pd.get_dummies(all_df)
?
#標準化numerical數據
#這一步并不是必要,但是得看你想要用的分類器是什么。一般來說,regression的分類器最好是把源數據給放在一個標準分布內。不要讓數據間的差距太大。這里,我們當然不需要把One-Hot的那些0/1數據給標準化。我們的目標應該是那些本來就是numerical的數據:
?
#先來看看哪些是numerical的:
numeric_cols= all_df.columns[all_df.dtypes != 'object']
#這里也是用log1p()方法進行平滑
all_dummy_df["樓齡"]=np.log1p(all_dummy_df["樓齡"])
all_dummy_df["建筑面積"]=np.log1p(all_dummy_df["建筑面積"])
?
四、 特征選擇
?
在特征工程部分,這么多維特征一方面可能會導致維數災難,另一方面很容易導致過擬合,需要做降維處理,降維方法常用的有如 PCA,t-SNE 等,這類方法的計算復雜度比較高。并且根據以往經驗,PCA 或t-SNE 效果往往不好。除了采用降維算法之外,也可以通過特征選擇來降低特征維度。特征選擇的方法很多:最大信息系數(MIC)、皮爾森相關系數(衡量變量間的線性相關性)、正則化方法(L1,L2)、基于模型的特征排序方法。比較高效的是最后一種,即基于學習模型的特征排序方法,這種方法有一個好處:模型學習的過程和特征選擇的過程是同時進行的,因此我們采用這種方法,基于xgboost 來做特征選擇,xgboost 模型訓練完成后可以輸出特征的重要性,據此我們可以保留Top N 個特征,從而達到特征選擇的目的。
?
五、 模型設計與分析
?
5.1單個分類器
從訓練數據中隨機劃分部分數據作為驗證集,剩下作為訓練集,供模型調參使用。在訓練集上采用十折交叉驗證進行模型調參。得到多個單模型結果后,再在驗證集上做進一步的模型融合。
#把數據集分回訓練/測試集
dummy_train_df= all_dummy_df[:110]
dummy_test_df= all_dummy_df[110:-1]
X_train= dummy_train_df.values
X_test= dummy_test_df.values
(1)隨機森林
max_features= [.1, .3, .5, .7, .9, .99]
test_scores= []
formax_feat in max_features:
??? clf =RandomForestRegressor(n_estimators=200, max_features=max_feat)
??? test_score = np.sqrt(-cross_val_score(clf,X_train, y_train, cv=5, scoring='neg_mean_squared_error'))
??? test_scores.append(np.mean(test_score))
plt.plot(max_features,test_scores)
plt.title("MaxFeatures vs CV Error");
#用RF的最優值達到了0.194
?
(2)這里我們用一個Stacking的思維來汲取兩種或者多種模型的優點首先,我們把最好的parameter拿出來,
做成我們最終的model
ridge= Ridge(alpha=1)
rf= RandomForestRegressor(n_estimators=500, max_features=.3)
ridge.fit(X_train,y_train)
rf.fit(X_train,y_train)
#上面提到了,因為最前面我們給label做了個log(1+x), 這里我們需要把predit的值給exp回去,并且減掉那個"1"
所以就是我們的expm1()函數。
y_ridge= np.expm1(ridge.predict(X_test))
y_rf= np.expm1(rf.predict(X_test))
#一個正經的Ensemble是把這群model的預測結果作為新的input,再做一次預測。
y_final= 0.8*y_ridge + 0.2*y_rf
print(y_final)
?
要判定一下當前模型所處狀態(欠擬合or過擬合)
?
有一個很可能發生的問題是,我們不斷地做feature engineering,產生的特征越來越多,用這些特征去訓練模型,會對我們的訓練集擬合得越來越好,同時也可能在逐步喪失泛化能力,從而在待預測的數據上,表現不佳,也就是發生過擬合問題。從另一個角度上說,如果模型在待預測的數據上表現不佳,除掉上面說的過擬合問題,也有可能是欠擬合問題,也就是說在訓練集上,其實擬合的也不是那么好。而在機器學習的問題上,對于過擬合和欠擬合兩種情形。我們優化的方式是不同的。
?
對過擬合而言,通常以下策略對結果優化是有用的:
- 做一下feature selection,挑出較好的feature的subset來做training
- 提供更多的數據,從而彌補原始數據的bias問題,學習到的model也會更準確
而對于欠擬合而言,我們通常需要更多的feature,更復雜的模型來提高準確度。
著名的learning curve可以幫我們判定我們的模型現在所處的狀態。我們以樣本數為橫坐標,訓練和交叉驗證集上的錯誤率作為縱坐標,兩種狀態分別如下兩張圖所示:過擬合,欠擬合
?
著名的learningcurve可以幫我們判定我們的模型現在所處的狀態。我們以樣本數為橫坐標,訓練和交叉驗證集上的錯誤率作為縱坐標
在實際數據上看,我們得到的learning curve沒有理論推導的那么光滑,但是可以大致看出來,訓練集和交叉驗證集上的得分曲線走勢還是符合預期的。目前的曲線看來,我們的model并不處于overfitting的狀態(overfitting的表現一般是訓練集上得分高,而交叉驗證集上要低很多,中間的gap比較大)。
?
5.2 Bagging
?
一般來說,單個分類器的效果真的是很有限。我們會傾向于把N多的分類器合在一起,做一個“綜合分類器”以達到最好的效果。模型融合可以比較好地緩解,訓練過程中產生的過擬合問題,從而對于結果的準確度提升有一定的幫助。Bagging把很多的小分類器放在一起,每個train隨機的一部分數據,然后把它們的最終結果綜合起來(多數投票制)。Sklearn已經直接提供了這套構架,我們直接調用就行:在這里,我們用CV結果來測試不同的分類器個數對最后結果的影響。注意,我們在部署Bagging的時候,要把它的函數base_estimator里填上你的小分類器
?
(1)用bagging自帶的DT
params= [1, 10, 15, 20, 25, 30, 40]
test_scores= []
forparam in params:
clf= BaggingRegressor(n_estimators=param)
?test_score = np.sqrt(-cross_val_score(clf,X_train, y_train, cv=10, scoring='neg_mean_squared_error'))
test_scores.append(np.mean(test_score))
???
plt.plot(params,test_scores)
plt.title("n_estimatorvs CV Error");
#用bagging自帶的DT,結果是0.196
?
(2)ridge + bagging
ridge= Ridge(1)
params= [1, 10, 15, 20, 25, 30, 40]
test_scores= []
forparam in params:
??? clf = BaggingRegressor(n_estimators=param,base_estimator=ridge)
??? test_score = np.sqrt(-cross_val_score(clf,X_train, y_train, cv=10, scoring='neg_mean_squared_error'))
??? test_scores.append(np.mean(test_score))
plt.plot(params,test_scores)
print(test_scores)
plt.title("n_estimatorvs CV Error");
?
#使用25個小ridge分類器的bagging,達到了0.163的結果.
#learning_curve曲線,準確率比較高,基本沒有過擬合
?
5.3Boosting
#Boosting比Bagging理論上更高級點,它也是攬來一把的分類器。但是把他們線性排列。下一個分類器把上一個分類器分類得不好的地方加上更高的權重,這樣下一個分類器就能在這個部分學得更加“深刻”。
?
(1)Adaboost+Ridge
params= [34,35,36,45,50,55,60,65]
test_scores= []
forparam in params:
??? clf = AdaBoostRegressor(n_estimators=param,base_estimator=ridge)
??? test_score = np.sqrt(-cross_val_score(clf,X_train, y_train, cv=10, scoring='neg_mean_squared_error'))
??? test_scores.append(np.mean(test_score))
plt.plot(params,test_scores)
plt.title("n_estimatorvs CV Error");??
#Adaboost+Ridge在這里,55個小分類器的情況下,達到了0.163的結果。其learning_curve曲線如下:
?
(2)使用Adaboost自帶的DT
params= [10, 15, 20, 25, 30, 35, 40, 45, 50]
test_scores= []
forparam in params:
??? clf = AdaBoostRegressor(n_estimators=param)
??? test_score = np.sqrt(-cross_val_score(clf,X_train, y_train, cv=10, scoring='neg_mean_squared_error'))
??? test_scores.append(np.mean(test_score))
plt.plot(params,test_scores)
plt.title("n_estimatorvs CV Error");
#test_scores達到0.198的結果
?
5.3XGBoost
#這依舊是一個Boosting框架的模型,但是卻做了很多的改進。用Sklearn自帶的cross validation方法來測試模型
params= [1,2,3,4,5,6]
test_scores= []
forparam in params:
??? clf = XGBRegressor(max_depth=param)
??? test_score = np.sqrt(-cross_val_score(clf,X_train, y_train, cv=10, scoring='neg_mean_squared_error'))
??? test_scores.append(np.mean(test_score))
?
plt.plot(params,test_scores)
plt.title("max_depthvs CV Error");
#當params值為6時,test_scores達到0.185
#輕微過擬合
?
5.4 GBM
params= [80,90,100,110,120,130,140,150]
test_scores= []
forparam in params:
??? clf =GradientBoostingRegressor(n_estimators=param)
??? test_score = np.sqrt(-cross_val_score(clf,X_train, y_train, cv=10, scoring='neg_mean_squared_error'))
??? test_scores.append(np.mean(test_score))
???
plt.plot(params,test_scores)
plt.title("max_depthvs CV Error");
?
#當params值為90時,test_scores達到0.188,從其學習率曲線可以看出,模型過擬合挺嚴重
#綜上,Bagging + Ridge 與Adaboost+Ridge模型的準確率和泛化能力是比較好。
六 使用ridge + bagging模型預測房價
?
ridge= Ridge(1)
br=BaggingRegressor(n_estimators=param, base_estimator=ridge)
br.fit(X_train,y_train)
#房價預測
y_br= np.expm1(br.predict(X_test))
?
features={'樓齡' , '建筑面積' , '區域_增城' , '區域_天河' , '區域_海珠’, '區域_番禺' , '區域_白云' ,? '樓層_中樓層' , '樓層_低樓層' , '樓層_獨棟' , '樓層_高樓層' , '朝向_東' , '朝向_東北' , '朝向_東南' , '朝向_東西' , '朝向_北' , '朝向_南' , '朝向_南北' , '朝向_西' , '朝向_西北' , '朝向_西南' , '房屋用途_別墅' , '房屋用途_普通住宅'}???
?
#使用GBM模型進行特征選擇
gbr= GradientBoostingRegressor(loss='ls', n_estimators=100, max_depth=3,verbose=1, warm_start=True)
gbr_fitted= gbr.fit(X_train, y_train)
?
#查看特征重要度,根據特征重要度可以進行特征選擇,選取重要度高的特征,刪除不重要的特征以減少維度降低過擬合
print(zip(features,list(gbr.feature_importances_)))
#可見建筑面積是影響房屋價格的最重要特征。
總結
以上是生活随笔為你收集整理的广州二手房价分析与预测的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微信页面跳转设计
- 下一篇: HTAP 能够取代 OLAP 吗?