房价预测(基于决策树算法)
預測波士頓房價
第一步. 導入數據
在這個項目中,將使用波士頓房屋信息數據來訓練和測試一個模型,并對模型的性能和預測能力進行評估。我們希望可以通過該模型實現對房屋的價值預估,提高房地產經紀人的工作效率。
此項目的數據集來自kaggle原始數據,未經過任何處理。該數據集統計了2006年至2010年波士頓個人住宅銷售情況,包含2900多條觀測數據(其中一半是訓練數據,即我們的housedata.csv文件)。更多文檔信息可以參考作者的文檔,以及項目附件data_description.txt文件(特征描述文件)。
下面區域的代碼用以載入一些此項目所需的Python庫。
# 載入此項目需要的庫 import numpy as np import pandas as pd import visuals as vs # Supplementary code 補充的可視化代碼import matplotlib.pyplot as plt import seaborn as sns plt.style.use('seaborn') # use seaborn style 使用seaborn風格import warnings warnings.filterwarnings('ignore')%matplotlib inline print('你已經成功載入所有庫!') 你已經成功載入所有庫!
加載數據
# 載入波士頓房屋的數據集:使用pandas載入csv,并賦值到data_df data_df = pd.read_csv('housedata.csv')# 成功載入的話輸出訓練數據行列數目 print("Boston housing dataset has {} data points with {} variables each.".format(*data_df.shape)) Boston housing dataset has 1460 data points with 81 variables each.
第二步. 數據分析
這個部分,將對已有的波士頓房地產數據進行初步的觀察與處理。
由于這個項目的最終目標是建立一個預測房屋價值的模型,需要將數據集分為特征(features)和目標變量(target variable)。
- 目標變量:'SalePrice',是我們希望預測的變量。
- 特征:除'SalePrice'外的屬性都是特征,它們反應了數據點在某些方面的表現或性質。
觀察數據
對波士頓房價的數據進行觀察,從而掌握更多數據本身的信息。
(1)使用 head方法 打印并觀察前7條data_df數據
# 打印出前7條data_df print(data_df.head(7)) Id MSSubClass MSZoning LotFrontage LotArea Street Alley LotShape \ 0 1 60 RL 65.0 8450 Pave NaN Reg 1 2 20 RL 80.0 9600 Pave NaN Reg 2 3 60 RL 68.0 11250 Pave NaN IR1 3 4 70 RL 60.0 9550 Pave NaN IR1 4 5 60 RL 84.0 14260 Pave NaN IR1 5 6 50 RL 85.0 14115 Pave NaN IR1 6 7 20 RL 75.0 10084 Pave NaN Reg LandContour Utilities ... PoolArea PoolQC Fence MiscFeature MiscVal \ 0 Lvl AllPub ... 0 NaN NaN NaN 0 1 Lvl AllPub ... 0 NaN NaN NaN 0 2 Lvl AllPub ... 0 NaN NaN NaN 0 3 Lvl AllPub ... 0 NaN NaN NaN 0 4 Lvl AllPub ... 0 NaN NaN NaN 0 5 Lvl AllPub ... 0 NaN MnPrv Shed 700 6 Lvl AllPub ... 0 NaN NaN NaN 0 MoSold YrSold SaleType SaleCondition SalePrice 0 2 2008 WD Normal 208500 1 5 2007 WD Normal 181500 2 9 2008 WD Normal 223500 3 2 2006 WD Abnorml 140000 4 12 2008 WD Normal 250000 5 10 2009 WD Normal 143000 6 8 2007 WD Normal 307000 [7 rows x 81 columns](2)Id特征對我們訓練數據沒有任何用處,在data_df中使用drop方法刪除'Id'列數據
# 刪除data_df中的Id特征(保持數據仍在data_df中,不更改變量名) data_df.drop('Id',axis=1,inplace=True)(3)使用describe方法觀察data_df各個特征的統計信息:
data_df.describe()| 1460.000000 | 1201.000000 | 1460.000000 | 1460.000000 | 1460.000000 | 1460.000000 | 1460.000000 | 1452.000000 | 1460.000000 | 1460.000000 | ... | 1460.000000 | 1460.000000 | 1460.000000 | 1460.000000 | 1460.000000 | 1460.000000 | 1460.000000 | 1460.000000 | 1460.000000 | 1460.000000 |
| 56.897260 | 70.049958 | 10516.828082 | 6.099315 | 5.575342 | 1971.267808 | 1984.865753 | 103.685262 | 443.639726 | 46.549315 | ... | 94.244521 | 46.660274 | 21.954110 | 3.409589 | 15.060959 | 2.758904 | 43.489041 | 6.321918 | 2007.815753 | 180921.195890 |
| 42.300571 | 24.284752 | 9981.264932 | 1.382997 | 1.112799 | 30.202904 | 20.645407 | 181.066207 | 456.098091 | 161.319273 | ... | 125.338794 | 66.256028 | 61.119149 | 29.317331 | 55.757415 | 40.177307 | 496.123024 | 2.703626 | 1.328095 | 79442.502883 |
| 20.000000 | 21.000000 | 1300.000000 | 1.000000 | 1.000000 | 1872.000000 | 1950.000000 | 0.000000 | 0.000000 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 2006.000000 | 34900.000000 |
| 20.000000 | 59.000000 | 7553.500000 | 5.000000 | 5.000000 | 1954.000000 | 1967.000000 | 0.000000 | 0.000000 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 5.000000 | 2007.000000 | 129975.000000 |
| 50.000000 | 69.000000 | 9478.500000 | 6.000000 | 5.000000 | 1973.000000 | 1994.000000 | 0.000000 | 383.500000 | 0.000000 | ... | 0.000000 | 25.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 6.000000 | 2008.000000 | 163000.000000 |
| 70.000000 | 80.000000 | 11601.500000 | 7.000000 | 6.000000 | 2000.000000 | 2004.000000 | 166.000000 | 712.250000 | 0.000000 | ... | 168.000000 | 68.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 8.000000 | 2009.000000 | 214000.000000 |
| 190.000000 | 313.000000 | 215245.000000 | 10.000000 | 9.000000 | 2010.000000 | 2010.000000 | 1600.000000 | 5644.000000 | 1474.000000 | ... | 857.000000 | 547.000000 | 552.000000 | 508.000000 | 480.000000 | 738.000000 | 15500.000000 | 12.000000 | 2010.000000 | 755000.000000 |
8 rows × 37 columns
數據預處理
數據不可能是百分百的‘干凈’數據(即有用數據),總會在采集整理時有些”失誤“、“冗余”,造成“臟”數據,所以要從數據的正確性和完整性這兩個方面來清理數據。
- 正確性:一般是指有沒有異常值,比如我們這個數據集中作者的文檔所說:
I would recommend removing any houses with more than 4000 square feet from the data set (which eliminates these five unusual observations) before assigning it to students.
建議我們去掉數據中'GrLivArea'中超過4000平方英尺的房屋(具體原因可以參考文檔),當然本數據集還有其他的異常點,這里不再處理。 - 完整性:采集或者整理數據時所產生的空數據造成了數據的完整性缺失,通常我們會使用一定的方法處理不完整的數據。在本例中,我們使用以下兩種方法,一是丟棄數據,即選擇丟棄過多空數據的特征(或者直接丟棄數據行,前提是NA數據占比不多),二是填補數據,填補的方法也很多,均值中位數眾數填充等等都是好方法。
正確性方面
以下代碼將使用matplotlib庫中的scatter方法 繪制'GrLivArea'和'SalePrice'的散點圖,x軸為'GrLivArea',y軸為'SalePrice',觀察數據**
# 繪制散點圖 plt.scatter(data_df['GrLivArea'],data_df['SalePrice']) plt.xlabel('GrLivArea') plt.ylabel('SalePrice') plt.show()**觀察所得:通過上圖我們可以看到那幾個異常值,即'GrLivArea'大于4000,但是'SalePrice'又極低的數據,所以需要從data_df刪除這幾個異常值。
刪除后重新繪制'GrLivArea'和'SalePrice'的關系圖,確認異常值已刪除。**
# 從data_df中刪除 GrLivArea大于4000 且 SalePrice低于300000 的值 index_del = data_df[(data_df['GrLivArea'] > 4000) & (data_df['SalePrice'] < 300000)].index data_df.drop(index=index_del, inplace=True)# 重新繪制GrLivArea和SalePrice的關系圖,確認異常值已刪除 plt.scatter(data_df['GrLivArea'],data_df['SalePrice']) plt.xlabel('GrLivArea') plt.ylabel('SalePrice') plt.show()
完整性方面
篩選出過多空數據的特征,這個項目定為篩選出有超過25%為空數據的特征
limit_percent = 0.25 limit_value = len(data_df) * limit_percent # 統計并打印出超過25%的空數據的特征 list(data_df.columns[data_df.isna().sum() > limit_value]) ['Alley', 'FireplaceQu', 'PoolQC', 'Fence', 'MiscFeature']接著,查看data_description.txt文件,就會發現,這些并非一定是空缺數據,而沒有游泳池,籬笆等也會用NA來表示,那么就不需要刪除這些特征了,而是用None來填充NA數據。
以下將使用fillna方法填充空數據。
# 確定所有空特征 missing_columns = list(data_df.columns[data_df.isnull().sum() != 0]) # 確定哪些是類別特征,哪些是數值特征 missing_numerical = list(data_df[missing_columns].dtypes[data_df[missing_columns].dtypes != 'object'].index) missing_category = [i for i in missing_columns if i not in missing_numerical] print("missing_numerical:",missing_numerical) print("missing_category:",missing_category) missing_numerical: ['LotFrontage', 'MasVnrArea', 'GarageYrBlt'] missing_category: ['Alley', 'MasVnrType', 'BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2', 'Electrical', 'FireplaceQu', 'GarageType', 'GarageFinish', 'GarageQual', 'GarageCond', 'PoolQC', 'Fence', 'MiscFeature'] # 需要填充眾數的特征 fill_Mode = ['Electrical'] # 需要填充None的特征 fill_None = ['Alley', 'MasVnrType', 'BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2', 'FireplaceQu', 'GarageType', 'GarageFinish', 'GarageQual', 'GarageCond', 'PoolQC', 'Fence', 'MiscFeature'] # 需要填充0的特征 fill_0 = ['GarageYrBlt'] # 需要填充中位數的特征 fill_median = ['LotFrontage', 'MasVnrArea']# 按需填補上面數據 data_df[fill_Mode] = data_df[fill_Mode].fillna(data_df[fill_Mode].mode()) data_df[fill_None] = data_df[fill_None].fillna('None') data_df[fill_0] = data_df[fill_0].fillna(0) data_df[fill_median] = data_df[fill_median].fillna(data_df[fill_median].mean())
特征分析
有這么一句話在業界廣泛流傳:特征數據決定了機器學習的上限,而模型和算法只是逼近這個上限而已。特征工程,是整個數據分析過程中不可缺少的一個環節,其結果質量直接關系到模型效果和最終結論。從上面兩步中我們得到了“干凈”的數據,但是data_df總共有81個特征,應當剔除那些無關緊要的特征(噪聲),使用真正關鍵的特征來進行模型訓練。現在需要我們對這些龐大的數據進行分析,提取出與目標最為關聯的數據。
繪制'SalePrice'的直方圖,觀察該直方圖屬于什么分布
# 繪制直方圖 plt.hist(data_df['SalePrice']) plt.xlabel('SalePrice') plt.show()觀察結論:'SalePrice'屬于正偏態分布。
如果特征極其多,很難清晰的看到特征與目標變量之間的關系,就需要利用統計知識來進行多變量分析了。常用的方法可使用熱圖heatmap結合corr方法來進行客觀分析,熱圖Heatmap可以用顏色變化來反映變量之間的相關性二維矩陣或說相關性表格中的數據信息,它可以直觀地將數據值的大小以定義的顏色深淺表示出來。
這個項目,為了簡化訓練,將以相關性絕對值大于0.5為界來選取所需要的特征。
corrmat = data_df.corr().abs() top_corr = corrmat[corrmat["SalePrice"]>0.5].sort_values(by = ["SalePrice"], ascending = False).index cm = abs(np.corrcoef(data_df[top_corr].values.T)) f, ax = plt.subplots(figsize=(20, 9)) sns.set(font_scale=1.3) hm = sns.heatmap(cm, cbar=True, annot=True,square=True, fmt='.2f', annot_kws={'size': 13}, yticklabels=top_corr.values, xticklabels=top_corr.values); data_df = data_df[top_corr]
接下來,我們從創造性方面來對我們的特征進行“改造”。
- 創造性:創造性主要是說兩種情況,一種是對現有數據的處理,比如對類別的獨熱編碼(One-hotEncoder)或者標簽編碼(LabelEncoder),數值的區間縮放,歸一化,標準化等等。另一種就是根據某一個或多個特征創造一個新的特征,例如某特征按組分類(groupby)后,或者某些特征組合后來創造新特征等等。
因為篩選出來的特征都為數值類型特征,所以只做標準化的操作:這個項目是一個回歸類型的項目,而回歸算法對標準正態分步預測較為準確,從目標數據可以看出數據是一個偏態分布,那么將使用log將數據從偏態分布轉換為標準正態分布,最后進行標準化。
from scipy.special import boxcox1p from sklearn.preprocessing import StandardScalerdata_df['SalePrice'] = np.log1p(data_df['SalePrice']) numeric_features = list(data_df.columns) numeric_features.remove('SalePrice') for feature in numeric_features:#all_data[feat] += 1data_df[feature] = boxcox1p(data_df[feature], 0.15)scaler = StandardScaler() scaler.fit(data_df[numeric_features]) data_df[numeric_features] = scaler.transform(data_df[numeric_features])
第三步. 建立模型
。
定義衡量標準
如果不能對模型的訓練和測試的表現進行量化地評估,就很難衡量模型的好壞。通常需要定義一些衡量標準,這些標準可以通過對某些誤差或者擬合程度的計算來得到。在這個項目中,將通過運算決定系數 R2R^2R2 來量化模型的表現。模型的決定系數是回歸分析中十分常用的統計信息,經常被當作衡量模型預測能力好壞的標準。
R2R^2R2 的數值范圍從0至1,表示目標變量的預測值和實際值之間的相關程度平方的百分比。一個模型的 R2R^2R2 值為0還不如直接用平均值來預測效果好;而一個 R2R^2R2 值為1的模型則可以對目標變量進行完美的預測。從0至1之間的數值,則表示該模型中目標變量中有百分之多少能夠用特征來解釋。模型也可能出現負值的 R2R^2R2,這種情況下模型所做預測有時會比直接計算目標變量的平均值差很多。
在下方代碼的 performance_metric 函數中,將實現:
- 使用 sklearn.metrics 中的 r2_score 來計算 y_true 和 y_predict 的 R2R^2R2 值,作為對其表現的評判。
- 將他們的表現評分儲存到 score 變量中。
擬合程度
假設一個數據集有五個數據且某一模型做出下列目標變量的預測:
| 3.0 | 2.5 |
| -0.5 | 0.0 |
| 2.0 | 2.1 |
| 7.0 | 7.8 |
| 4.2 | 5.3 |
提示:R2R^2R2 分數是指可以從自變量中預測的因變量的方差比例。 換一種說法:
- R2R^2R2 為0意味著因變量不能從自變量預測。
- R2R^2R2 為1意味著可以從自變量預測因變量。
- R2R^2R2 在0到1之間表示因變量可預測的程度。
- R2R^2R2 為0.40意味著 Y 中40%的方差可以從 X 預測。
下方的代碼將使用 performance_metric 函數來計算 y_true 和 y_predict 的決定系數。
# 計算這一模型的表現 score = performance_metric([3, -0.5, 2, 7, 4.2], [2.5, 0.0, 2.1, 7.8, 5.3]) print("Model has a coefficient of determination, R^2, of {:.3f}.".format(score)) Model has a coefficient of determination, R^2, of 0.923.結論:這個模型已經成功地描述目標變量的變化了。因為R2R^2R2 分數已高達0.923,說明因變量的可預測程度非常高。
數據分割與重排
接下來,將分割波士頓房屋數據集,包括特征與目標變量、訓練集和測試集。通常在這個過程中,數據也會被重排,以消除數據集中由于順序而產生的偏差。
將data_df分割為特征和目標變量
# 分割 labels = data_df['SalePrice'] #TODO:提取SalePrice作為labels features = data_df.drop(['SalePrice'], axis=1) #TODO:提取除了SalePrice以外的特征賦值為features
下方代碼將實現:
- 使用 sklearn.model_selection 中的 train_test_split, 將 features 和 prices 的數據都分成用于訓練的數據子集和用于測試的數據子集。
- 分割比例為:80%的數據用于訓練,20%用于測試;
- 選定一個數值以設定 train_test_split 中的 random_state ,這會確保結果的一致性;
- 將分割后的訓練集與測試集分配給 X_train, X_test, y_train 和 y_test。
訓練及測試
-
將數據集按一定比例分為訓練用的數據集和測試用的數據集對學習算法能在一定程度上避免過擬合。
-
如果用模型已經見過的數據,例如部分訓練集數據進行測試,會使得計算準確率時,這個得分會不可靠。
第四步. 分析模型的表現
在項目的第四步,我們來觀察不同參數下,模型在訓練集和驗證集上的表現。這里,我們專注于一個特定的算法(帶剪枝的決策樹DecisionTreeRegressor)和這個算法的一個參數 'max_depth'。
接下來,用全部訓練集訓練,選擇不同'max_depth' 參數,觀察這一參數的變化如何影響模型的表現。并畫出模型的表現來分析。
學習曲線
下方區域內的代碼會輸出四幅圖像,它們是一個決策樹模型在不同最大深度下的表現。每一條曲線都直觀得顯示了隨著訓練數據量的增加,模型學習曲線在訓練集評分和驗證集評分的變化,評分使用決定系數 R2R^2R2。曲線的陰影區域代表的是該曲線的不確定性(用標準差衡量)。
# Produce learning curves for varying training set sizes and maximum depths vs.ModelLearning(features, labels)學習曲線結論觀察
- 對于上述圖像中的最大深度為 3 的那個,隨著訓練數據量的增加,訓練集曲線的評分減少,驗證集曲線的增加。
- 如果有更多的訓練數據,也無法提升模型的表現,因訓練集曲線和驗證集曲線已相交在一個數值。
復雜度曲線
下列代碼內的區域會輸出一幅圖像,它展示了一個已經經過訓練和驗證的決策樹模型在不同最大深度條件下的表現。這個圖形將包含兩條曲線,一個是訓練集的變化,一個是驗證集的變化。跟學習曲線相似,陰影區域代表該曲線的不確定性,模型訓練和測試部分的評分都用的 performance_metric 函數。
vs.ModelComplexity(X_train, y_train)
偏差(bias)與方差(variance)之間的權衡取舍
提示: 高偏差表示欠擬合(模型過于簡單),而高方差表示過擬合(模型過于復雜,以至于無法泛化)。
觀察結論:
- 當模型以最大深度 1訓練時,模型的預測出現了很大的偏差。
- 當模型以最大深度10訓練時,模型的預測出現了很大的方差。
- 當模型以最大深度 1訓練時,訓練集得分和驗證集得分都較低,可見是欠擬合的情況。當模型以最大深度10訓練時,訓練集得分極高和驗證集得分不太高,而兩個得分相差甚大,可見是過擬合的情況。
最優模型的猜測
- 結合復雜度曲線,可見最大深度是 4 的模型能夠最好地對未見過的數據進行預測。
- 依據:隨著最大深度的增加,訓練集得分和驗證集得分都在增加,但當最大深度超過4后,驗證集得分反而有下降的趨勢,說明模型變得越來越復雜,逐漸變得過擬合。
第五步. 評估模型的表現
在項目的最后一節中,將構建一個模型,并使用 fit_model 中的優化模型去預測客戶特征集。
網格搜索(Grid Search)
- 網格搜索法是窮舉所有參數的組合,找出使模型得分最高的那個組合。比如在決策樹模型里,分別用幾個不同的深度參數去訓練模型并計算其測試集得分,測試集得分最高模型對應的深度參數便是最優參數。
- 優化模型方法:嘗試所有參數的組合,以發現能使模型性能最好的參數組合。
交叉驗證
- K折交叉驗證法(k-fold cross-validation)是隨機將訓練集劃分成K份,依次將其中的一份作為驗證集,其余的作為訓練集,訓練K個模型,最后選擇模型表現得最好的那一個。
- GridSearchCV 是通過交叉驗證得到每個參數組合的得分,以確定最優的參數組合。
- GridSearchCV 中的'cv_results_'屬性能生成一個字典,記錄每組網格參數每次的訓練結果,包括訓練/驗證時間、訓練/驗證評估分數以及相關時間和評分的統計信息。
- K折交叉驗證可以盡可能地嘗試所有的數據集劃分方式,使網格搜索的結果可信度更高。K折交叉驗證取多次結果的平均值可以避免樣本劃分不合理的情況。
訓練最優模型
在這一步中,將使用決策樹算法訓練一個模型。為了得出的是一個最優模型,需要使用網格搜索法訓練模型,以找到最佳的 'max_depth' 參數。可以把'max_depth' 參數理解為決策樹算法在做出預測前,允許其對數據提出問題的數量。
在下方 fit_model 函數中,將實現:
將 ‘performance_metric’ 作為參數傳至這個函數中;
第六步. 做出預測
當我們用數據訓練出一個模型,它就可用于對新的數據進行預測。在我們的例子–決策樹回歸函數中,模型已經學會對新輸入的數據“提問”,并返回對目標變量的預測值。現在可以用這些預測來獲取未知目標變量的數據的信息,但是,輸入的新數據必須不能是已有訓練數據之中的。
最優模型
下方代碼將決策樹回歸函數代入訓練數據的集合,以得到最優化的模型。
# Fit the training data to the model using grid search reg = fit_model(X_train, y_train)# Produce the value for 'max_depth' print("Parameter 'max_depth' is {} for the optimal model.".format(reg.get_params()['max_depth'])) Parameter 'max_depth' is 6 for the optimal model.最終,使用確認好的參數來對測試數據進行預測,并來看看訓練結果如何。
depth = 6 regressor = DecisionTreeRegressor(max_depth = depth) regressor.fit(X_train, y_train) y_pred = regressor.predict(X_test) score = performance_metric(y_test, y_pred) print("The R2 score is ",score) The R2 score is 0.7520017488593774
訓練結果情況:
-
模型的效果并不理想。
-
改進:1、需要更多的特征來訓練模型;2、數據預處理時,空值的填充用錯數值;3、試試換成線性回歸模型。
總結
以上是生活随笔為你收集整理的房价预测(基于决策树算法)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ActiveX控件缺失解决方法
- 下一篇: 广州2年php8k,2年后,那位月入30