Kaggle入门之房屋价格预测

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_26658823/article/details/79085736

房价预测:高级回归技术

竞赛描述

要求一个购房者描述心目中的好房子,他们可能不会从地下室的高度或者与铁路的远近开始说起。这个训练赛的数据集证明,价格谈判比卧室或栅栏数量更重要。数据集中共有79个解释性变量描述了住宅的每一个方面,比赛要求是对每个房子的最终价格进行预测。

第一步:检查源数据集

首先引用numpy、pandas等必要模块,然后通过pandas的read_csv函数读入训练数据和测试数据。一般来说,数据集中的Id那一栏没有什么用,我们可以将其作为索引,之后检索起来也方便一些。

import numpy as np
import pandas as pd
from pandas import Series, DataFrame

import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
train_df = pd.read_csv('./data/train.csv',index_col=0)
test_df = pd.read_csv('./data/test.csv',index_col=0)
接下来查看训练数据的前几条记录。结果显示,训练数据一共有80列,除去最后我们要预测的SalePrice那一列,一共有79个解释变量。
train_df.head()
MSSubClass MSZoning LotFrontage LotArea Street Alley LotShape LandContour Utilities LotConfig PoolArea PoolQC Fence MiscFeature MiscVal MoSold YrSold SaleType SaleCondition SalePrice
Id
1 60 RL 65.0 8450 Pave NaN Reg Lvl AllPub Inside 0 NaN NaN NaN 0 2 2008 WD Normal 208500
2 20 RL 80.0 9600 Pave NaN Reg Lvl AllPub FR2 0 NaN NaN NaN 0 5 2007 WD Normal 181500
3 60 RL 68.0 11250 Pave NaN IR1 Lvl AllPub Inside 0 NaN NaN NaN 0 9 2008 WD Normal 223500
4 70 RL 60.0 9550 Pave NaN IR1 Lvl AllPub Corner 0 NaN NaN NaN 0 2 2006 WD Abnorml 140000
5 60 RL 84.0 14260 Pave NaN IR1 Lvl AllPub FR2 0 NaN NaN NaN 0 12 2008 WD Normal 250000

5 rows × 80 columns

第二步:数据平滑及合并

由于是对房屋价格进行预测,所以我们要注意价格的分布。通过下面的图片可以发现,房屋价格集中在200000附近的区间,这是一个比较偏的分布。为了使我们最后的更加精确,我们需要对价格进行平滑。这里我们使用log(price+1)来进行数据的平滑,可以看出,进行平滑之后的价格比较符合正态分布。

prices = DataFrame({"price":train_df.SalePrice, 
                    "log(price + 1)":np.log1p(train_df.SalePrice)})
prices.hist()
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x000001D5345E8080>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x000001D534650668>]], dtype=object)

png

为了方面后面的处理,我们把训练数据和测试数据合并成一个DataFrame。由于Kaggle比赛会将测试数据一起给我们,所以我们可以这样处理。在实际的机器学习应用中,测试数据一般是不知道的,也就不能进行这样的处理。

y_train = np.log1p(train_df.pop('SalePrice'))
all_df = pd.concat((train_df, test_df), axis=0)
all_df.head()
MSSubClass MSZoning LotFrontage LotArea Street Alley LotShape LandContour Utilities LotConfig ScreenPorch PoolArea PoolQC Fence MiscFeature MiscVal MoSold YrSold SaleType SaleCondition
Id
1 60 RL 65.0 8450 Pave NaN Reg Lvl AllPub Inside 0 0 NaN NaN NaN 0 2 2008 WD Normal
2 20 RL 80.0 9600 Pave NaN Reg Lvl AllPub FR2 0 0 NaN NaN NaN 0 5 2007 WD Normal
3 60 RL 68.0 11250 Pave NaN IR1 Lvl AllPub Inside 0 0 NaN NaN NaN 0 9 2008 WD Normal
4 70 RL 60.0 9550 Pave NaN IR1 Lvl AllPub Corner 0 0 NaN NaN NaN 0 2 2006 WD Abnorml
5 60 RL 84.0 14260 Pave NaN IR1 Lvl AllPub FR2 0 0 NaN NaN NaN 0 12 2008 WD Normal

5 rows × 79 columns

all_df.shape
(2919, 79)

第三步:数据转换

数据集合并之后,我们就可以对整个数据集进行处理了。首先我们要注意变量的类型。比如,MSSubClass是一个类别型的变量而非数值型变量,尽管它是由数字来表示的。pandas会将这类数字符号当成数字处理。这样会使我们后面的模型训练不准确,所以我们要把它变回string类型。

all_df.MSSubClass.dtypes
dtype('int64')
all_df.MSSubClass = all_df.MSSubClass.astype(str)
all_df.MSSubClass.value_counts()
20     1079
60      575
50      287
120     182
30      139
70      128
160     128
80      118
90      109
190      61
85       48
75       23
45       18
180      17
40        6
150       1
Name: MSSubClass, dtype: int64

把它变回string类型之后,我们的工作并没有完成。机器学习的模型处理类别型数据还是比较麻烦,我们还得将它变成数值型变量。这里,我们通过One-Hot(独热码)编码进行数据变换。在pandas中,可以使用get_dummies实现这一转换:

pd.get_dummies(all_df.MSSubClass,prefix='MSSubClass').head()
MSSubClass_120 MSSubClass_150 MSSubClass_160 MSSubClass_180 MSSubClass_190 MSSubClass_20 MSSubClass_30 MSSubClass_40 MSSubClass_45 MSSubClass_50 MSSubClass_60 MSSubClass_70 MSSubClass_75 MSSubClass_80 MSSubClass_85 MSSubClass_90
Id
1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0
2 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0
4 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
5 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0

上面的结果显示,我们把MSSubClass这一列扩展成了很多列,每一列只有0和1这两个值,这样我们就实现了类别型数据到数值型数据的转变。根据数据描述,有很多列都是类别型数据,所以我们要对所有类别型数据进行变换:

all_dummy_df = pd.get_dummies(all_df)
all_dummy_df.head()
LotFrontage LotArea OverallQual OverallCond YearBuilt YearRemodAdd MasVnrArea BsmtFinSF1 BsmtFinSF2 BsmtUnfSF SaleType_ConLw SaleType_New SaleType_Oth SaleType_WD SaleCondition_Abnorml SaleCondition_AdjLand SaleCondition_Alloca SaleCondition_Family SaleCondition_Normal SaleCondition_Partial
Id
1 65.0 8450 7 5 2003 2003 196.0 706.0 0.0 150.0 0 0 0 1 0 0 0 0 1 0
2 80.0 9600 6 8 1976 1976 0.0 978.0 0.0 284.0 0 0 0 1 0 0 0 0 1 0
3 68.0 11250 7 5 2001 2002 162.0 486.0 0.0 434.0 0 0 0 1 0 0 0 0 1 0
4 60.0 9550 7 5 1915 1970 0.0 216.0 0.0 540.0 0 0 0 1 1 0 0 0 0 0
5 84.0 14260 8 5 2000 2000 350.0 655.0 0.0 490.0 0 0 0 1 0 0 0 0 1 0

5 rows × 303 columns

全部转换之后,我们的数据集从79列增加到303列,所有类别型的数据都已经转换成数值型。下面我们进行缺失值的处理。

all_dummy_df.isnull().sum().sort_values(ascending=False).head()
LotFrontage     486
GarageYrBlt     159
MasVnrArea       23
BsmtHalfBath      2
BsmtFullBath      2
dtype: int64

可以看到,缺失最多的列是LotFrotage,其次是GarageYrBlt。处理缺失信息需要我们好好审题。一般来说,数据集的描述中会写清楚这些缺失代表什么。当然,如果没有写明,也只能靠猜了…这里,我们用平均值填补缺失值。

mean_cols = all_dummy_df.mean()
mean_cols.head(10)
LotFrontage        69.305795
LotArea         10168.114080
OverallQual         6.089072
OverallCond         5.564577
YearBuilt        1971.312778
YearRemodAdd     1984.264474
MasVnrArea        102.201312
BsmtFinSF1        441.423235
BsmtFinSF2         49.582248
BsmtUnfSF         560.772104
dtype: float64
all_dummy_df = all_dummy_df.fillna(mean_cols)
all_dummy_df.isnull().sum().sum()
0

接着,我们将数值型数据标准化。在进行回归预测时,我们应该尽量把数据集放在一个标准分布内,不能让数据之间的差距太大。当然,我们不需要把进行One-Hot编码的那些数据标准化,我们的目标是那些本来就是数值型的数据。下面先来看看哪些数据本来就是数值型的:

numeric_cols = all_df.columns[all_df.dtypes != 'object']
numeric_cols
Index(['LotFrontage', 'LotArea', 'OverallQual', 'OverallCond', 'YearBuilt',
       'YearRemodAdd', 'MasVnrArea', 'BsmtFinSF1', 'BsmtFinSF2', 'BsmtUnfSF',
       'TotalBsmtSF', '1stFlrSF', '2ndFlrSF', 'LowQualFinSF', 'GrLivArea',
       'BsmtFullBath', 'BsmtHalfBath', 'FullBath', 'HalfBath', 'BedroomAbvGr',
       'KitchenAbvGr', 'TotRmsAbvGrd', 'Fireplaces', 'GarageYrBlt',
       'GarageCars', 'GarageArea', 'WoodDeckSF', 'OpenPorchSF',
       'EnclosedPorch', '3SsnPorch', 'ScreenPorch', 'PoolArea', 'MiscVal',
       'MoSold', 'YrSold'],
      dtype='object')

这里,我们用

XX¯s
这种方式进行数据平滑。
numeric_col_means = all_dummy_df.loc[:, numeric_cols].mean()
numeric_col_std = all_dummy_df.loc[:, numeric_cols].std()
all_dummy_df.loc[:, numeric_cols] = (all_dummy_df.loc[:, numeric_cols] - numeric_col_means) / numeric_col_std

第四步:模型训练

首先,把数据集分回训练集和测试集:

dummy_train_df = all_dummy_df.loc[train_df.index]
dummy_test_df = all_dummy_df.loc[test_df.index]
dummy_train_df.shape, dummy_test_df.shape
((1460, 303), (1459, 303))

然后用不同的模型进行训练。

Ridge Regression

我们首先把数据放到岭回归模型里面跑一遍看看(对于多因子的数据集,这种模型可以把所有变量都无脑地丢进去)。

from sklearn.linear_model import Ridge
from sklearn.model_selection import cross_val_score
X_train = dummy_train_df.values
X_test = dummy_test_df.values
用sklearn自带的交叉验证方法来测试模型:
alphas = np.logspace(-3,2,50)
test_scores = list()
for alpha in alphas:
    clf = Ridge(alpha)
    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(alphas, test_scores)
plt.title("Alpha vs CV Error")
<matplotlib.text.Text at 0x1d534eff2b0>

png

根据图象可以看出,alpha在15左右时,岭回归模型的错误率比较低,大概在0.136左右。

Random Forest

再试试随机森林。

from sklearn.ensemble import RandomForestRegressor
max_features = [.1, .3, .5, .7, .9, .99]
test_scores = list()
for max_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("Max Features vs CV Error")
<matplotlib.text.Text at 0x1d5340f1898>

png

当max_feature为0.3时,随机森林的错误最小,大概在0.138左右。

Bagging

下面把alpha=0.5的岭回归模型作为基分类器,看一下Bagging的效果:

from sklearn.ensemble import BaggingRegressor
params = [1, 10, 15, 20, 25, 30, 40]
test_scores = list()
for param in params:
    clf = BaggingRegressor(n_estimators=param, base_estimator=Ridge(15))
    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("Params vs CV Error")
<matplotlib.text.Text at 0x1d5342e92e8>

图片显示Bagging的效果还是不错的,在包含25个基分类器的时候错误率约为0.133。

AdaBoost

最后看一下AdaBoost的效果,基分类器仍然是Ridge(15)

from sklearn.ensemble import AdaBoostRegressor
params = [10, 15, 20, 25, 30, 35, 40, 45, 50]
test_scores = list()
for param in params:
    clf = AdaBoostRegressor(n_estimators=param, base_estimator=Ridge(15))
    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("Params vs CV Error")
<matplotlib.text.Text at 0x1d53438ed30>

这里写图片描述

额……结果比较奇怪,随着基分类器数目的增多,错误率居然会上升……估计是代码有问题吧,以后再改吧:P

最后,我们以alpha=15的岭回归模型作为基分类器,训练包括25个基分类器的Bagging模型。

bg = BaggingRegressor(n_estimators=25,base_estimator=Ridge(15))
bg.fit(X_train, y_train)
y_final = np.expm1(bg.predict(X_test))
submission_df = DataFrame(data={"Id":test_df.index, 'SalePrice':y_final})
submission_df.head()
Id SalePrice
0 1461 116117.466902
1 1462 148436.121949
2 1463 173054.894723
3 1464 197418.537732
4 1465 195077.736535
submission_df.to_csv('./data/submission.csv',index=False)

提交之后的均方根误差(RMSE)为0.12133,排名前28%。

展开阅读全文

没有更多推荐了,返回首页