机器学习入门实战——使用回归技术预测房价

使用回归技术预测房价

问题描述与数据观察

我从kaggle上找了一个问题,链接为房价预测。大概的意思就是,根据房子的一系列信息,包括面积,地段,环境等等,我来预测它的房价。它告诉了我一部分已知特征和房价的房子作为训练集,给出另外一部分只知特征的房子,让我预测出它们的房价。

下载的数据有三个csv文件,一个是训练数据,一个是测试数据,另外一个数据提交的样例。
训练数据为1460x80的一个表结构,有1460个观测,79个特征。第一列为索引,剩下79列为特征,最后一列为房价,如下图所示。

测试数据为1459x79的一个表结构,解释同训练数据,只是没有最后一列的房价。
有一个专门的文件解释每一列数据的含义,比如说MSSubClass列表示住房类型,不同数字表示不同的类型。

我细细研读和观察了一波数据。发现数据基本上分为数值类型和文本类型,还有一些缺失。数值类型除第一个特征MSSubClass只是一个编号外,其余都是连续型数据,这一列数据我应该视作文本来处理。文本型的数据特征项的取值域不是太大(这为我做dummpy提供了方便)。房价数据从肉眼上没看出什么,可能需要通过可视化找出一些规律。

数据的预处理

拿到这样一个数据,为了将数据统一化处理,我首先训练数据中的价格列拿了出去,并将训练数据和测试数据按列纵向拼接在一起,以便进一步处理。合并后的数据规模为2919x79。

对于价格数据,我绘制出柱状图后发现有左偏,如图[pic2]我使用对数函数对其处理,使之分布更加均匀(极差变小的同时,左偏的数据取log处理,重心右挪)。对数变换即使用 log(x+1) log ( x + 1 ) ,加1是防止对数变换基数出现零的情况。这样处理之后,最后预测的结果要做相反方向的处理,即 ex1 e x − 1

这里写图片描述

对于一般的数值类型的特征,对于第一个特征MSSubClass,因为它的数值是表示住房类型,没有数值上的含义,所以将之转化为非数值型的对象类型,等同于文本。对于其他的数值特征,我进行标准化处理,即减去均值后除以标准差( (XX)/s ( X − X ′ ) / s ),使之均值为0,方差为1(标准差也为1)。这么做让我的数据点更平滑,更便于计算。需要注意的是,我这里也是可以继续使用Log的,这只是另一种“使数据平滑”的办法。

对于文本型数据(包括MSSubClass列),因为其取值域不是太大,我可以使用dummy方法,将一个特征转化为二值的向量,即对每一文本特征创建一组新的伪特征,对应其所有可能的取值。这些特征中只有这条数据对应的取值为1,其他都为0。缺失值既可以当成一个新的取值生成一个新的特征,也可以直接忽略,我这里直接忽略的方式,即缺失值dummy出来全是0。pandas自带的get_dummies方法,可以帮我一键做到One-Hot。

还有一类数据是缺失值,文本类型dummy出来不会有缺失。对于数值类型的缺失值,因为每一个特征的缺失值或者没有,或者不是太多,我用平均值来填充。处理缺失的信息,得靠好好审题。一般来说,数据集的描述里会写的很清楚,这些缺失都代表着什么。但是,我并没有找到相关的描述,故而用平均值来处理。

如此处理完之后的拼接数据,一共有303个数据特征,原来伪79个,dummy之后数据特征增多。不含缺失值。

以上操作对应的python脚本如下:

# -*- coding: utf-8 -*-
import numpy as np#导入numpy包和pandas包
import pandas as pd
train_df = pd.read_csv('input/train.csv', index_col=0)#读入迅雷数据,并将第0列作为索引index,DataFrame是pandas的表数据结构
test_df = pd.read_csv('input/test.csv', index_col=0)#读入测试数据
train_df.head()#展示训练数据
# matplotlib inline
prices = pd.DataFrame({"price":train_df["SalePrice"],
"log(price + 1)":np.log1p(train_df["SalePrice"])})
#处理训练数据的价格列,并生成一个DF表结构
prices.hist()#将其绘制柱状图
y_train = np.log1p(train_df.pop('SalePrice'))#将训练数据价格列弹出(减少一列),进行加1取对数处理,同prices第二列
all_df = pd.concat((train_df, test_df), axis=0)#按行拼接训练数据和测试数据,axis: 需要合并链接的轴,0是行,1是列
all_df.shape#展示一下规模
y_train.head()#展示前几个训练集y数据
all_df['MSSubClass'].dtypes#查看MSSubClass列的数据类型
all_df['MSSubClass'] = all_df['MSSubClass'].astype(str)#类型强制转换为字符串
all_df['MSSubClass'].value_counts()#计数,相同的有几次,按倒序排列
temp = pd.get_dummies(all_df['MSSubClass'], prefix='MSSubClass').head()#get_dummies构建词向量表示,坐标为词集合
all_dummy_df = pd.get_dummies(all_df)#将object数据类型的列都展开为二值表示,因为MSSubClass的数值没啥意义(住宅类型,类似于一种型号编码),也转
all_dummy_df.head()#展示前5行
all_dummy_df.isnull().sum().sort_values(ascending=False).head(10)#计算每一列的缺失按降序排列
mean_cols = all_dummy_df.mean()#计算每一列的平均值
mean_cols.head(10)#展示平均值的前10个
all_dummy_df = all_dummy_df.fillna(mean_cols)#用平均值填补缺失
all_dummy_df.isnull().sum().sum()#看看还有没有缺失
numeric_cols = all_df.columns[all_df.dtypes != 'object']#找出数值类型的列,这时候MSSubClass已经处理了
numeric_col_means = all_dummy_df.loc[:, numeric_cols].mean()#loc——通过行标签索引行数据 ,求数值列的均值
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数据的数值列标准化,即均值除以标准差
dummy_train_df = all_dummy_df.loc[train_df.index]#取出dummy的训练数据
dummy_test_df = all_dummy_df.loc[test_df.index]#取出dummy的测试数据
dummy_train_df.shape, dummy_test_df.shape#看看他们的形状

数据的建模与集成

有了处理后的数据,下一步我要做的就是建立模型。基于这个问题,我做了岭回归、随机森林、基于ridge回归model
的bagging集成学习、基于ridge回归模型的AdaBoost回归集成和基于ridge回归的XGB集成模型,最后取五个模型的平均值作为最终的结果。在这个问题中,ridge是很适合作为Ensemble的Base
Model。

  • 岭回归
    岭回归,Ridge Regression,是加入L2正则的最小二乘,Sklearn库提供了函数Ridge(alpha),alpha是超参数,是正则化项的系数,用来弱化变量参数共线性,限制变量权重(参数)过大,alpha越大,越不容易过拟合。
    导入岭回归相关库,把DataFrame转化成Numpy Array,用Sklearn自带的cross
    validation方法来测试模型,选用负均方误差(neg_mean_squared_error)作为score,得分是负数,这里的neg_mean_squared_error是一种奖励函数,优化的目标的使其最大化,而分类错误率是一种损失函数,优化的目标是使其最小化,所以取得分的相反数再开方,即均方根误差。
    这几个模型我都没有自己编写代码实现内部算法,直接利用的python包sklearn的接口,调用其中的算法。岭回归的代码如下,惩罚系数 alpha a l p h a 的选择,我采用了交叉验证的方法,发现其最佳参数约为19.307。

    from sklearn.linear_model import Ridge#从sklearn中导入岭回归的包
    from sklearn.model_selection import cross_val_score#从sklearn中导入交叉验证的包
    X_train = dummy_train_df.values#训练集所有数据数值化
    X_test = dummy_test_df.values#测试集所有数据数值化
    alphas = np.logspace(-3, 2, 50)#先等距划分,再以10为底指数化,就是说log完后是线性的,让岭回归的惩罚系数呈指数增长
    test_scores = []#用空集初始化
    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))
    import matplotlib.pyplot as plt#导入pyplot
    #%matplotlib inline
    plt.plot(alphas, test_scores)#看图直观看岭回归系数和均方误差的关系,找出最低值点
    plt.title("Alpha vs CV Error");
    ridge_param = alphas[test_scores.index(min(test_scores))]#最佳参数约为19.307
    
  • 随机森林
    利用RandomForestRegressor预测房价。RandomForestRegressor的参数主要是n_estimators 和 max_features。前者(n_estimators)是森林里树的数量,通常数量越大,效果越好,但是计算时间也会随之增加。当树的数量超过一个临界值之后,算法的效果并不会很显著地变好。后者(max_features)是根据节点划分的特征的最大值。这个值越低,方差减小得越多,但是偏差的增大也越多。在随机森林中,默认使用自助采样法(bootstra = True)(有放回重复采样)。
    同样地,随机森林模型也采用sklearn集成算法中的随机森林回归模块,使用交叉验证得到在训练每棵树时最多选择的特征数量max_features的最优值为0.3。代码如下:

    from sklearn.ensemble import RandomForestRegressor#导入随机森林回归的包
    max_features = [.1, .3, .5, .7, .9, .99]#最大特征
    test_scores = []#得分
    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");
    rm_param = max_features[test_scores.index(min(test_scores))]#最佳参数约为0.3
    
  • Bagging
    Bagging的训练集选择是随机的,均匀采样,各轮训练集之间相互独立,基础学习器没有权重,并行生成,最后的预测对分类问题一般采用投票方式,对回归问题采用简单平均方法。BaggingRegressor的主要参数有n_estimators,表示基础分类器个数,base_estimator表示基础分类器模型,默认是决策树DecisionTree。采用基础分类器类型为前面的岭回归模型。
    通过交叉验证,得到Bagging回归的最优参数为25,代码如下:

    ridge = Ridge(alpha=ridge_param)#alpha为15的岭回归,这是前面测出来的
    from sklearn.ensemble import BaggingRegressor#导入sklearn的集成包中的bagging回归
    from sklearn.model_selection import cross_val_score#交叉验证
    params = [1, 10, 15, 20, 25, 30, 40]#找bagging回归的最优参数
    test_scores = []
    for param 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))
    import matplotlib.pyplot as plt
    #%matplotlib inline
    plt.plot(params, test_scores)#看图可知,最优参数使20
    plt.title("n_estimator vs CV Error");
    bagging_param = params[test_scores.index(min(test_scores))]#最佳参数约为25
    
  • Boosting
    Boosting集成法是Bagging外的另外一类集成方法,Adaboost是其中较为流行的一类。
    Boostlng的训练集的选择是独立的,根据错误率采样,各轮训练集的选择与前轮的学习结果有关,基础学习器是串行生成。同Bagging,它也是揽来一把的分类器,不过需要线性排列,下一个分类器把上一个分类器分类得不好的地方加上更高的权重,这样下一个分类器就能在这个部分学得更加“深刻”。对正确分类的样本降权,对错误分类的样本加权(而这些样本通常就是分类边界附近的样本),最后分类器是多个弱分类器的加权组合(线性叠加),通过提升弱分类器的分类精度提升学习器的性能。这里boosting选用AdaBoostRegressor时,用法同BaggingRegressor。
    同样地,使用岭回归作为基础模型,交叉验证得到Bagging的最优参数为5,代码如下:

    from sklearn.ensemble import AdaBoostRegressor#AdaBoostRegressor回归也来一组参数,最优参数35
    params = [1, 2, 3, 4,5,6,7,8,9,10]
    test_scores = []
    for param 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_estimator vs CV Error");
    adaboost_param = params[test_scores.index(min(test_scores))]#最佳参数约为5
    
  • XGboost
    尝试一下XGBoost,使用XGBoost是因为kaggle上人人都在用。可见这是在Kaggle运用效果比较好的一款Boosting框架的模型。
    xgboost的安装最为便捷的一种方法如下:xgboost安装步骤。看好对应的适合的文件下载安装即可。
    XGboost也是一个boosting的方法,交叉验证可知,在最大树深为5的时候,达到最佳效果,代码如下:

      from xgboost import XGBRegressor#XGB回归也来一组参数
    params = [4,5,6,7,8,9,10]
    test_scores = []
    for param in params:
        clf = XGBRegressor(max_depth=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))
    import matplotlib.pyplot as plt
    #%matplotlib inline
    plt.plot(params, test_scores)
    plt.title("max_depth vs CV Error");
    xgb_param = params[test_scores.index(min(test_scores))]#最佳参数约为5
    

我分别使用最优参数的回归分类器取拟合训练数据得到模型,再使用模型去做预测,最后将预测结果取平均值并按要求存为csv文件提交。代码如下:

####################################

ridge = Ridge(alpha=ridge_param)#alpha为15的岭回归,这是前面测出来的
rf = RandomForestRegressor(n_estimators=1000, max_features=rm_param)#最优随机森林
bagging = BaggingRegressor(n_estimators=bagging_param, base_estimator=ridge)
adaboost = AdaBoostRegressor(n_estimators=adaboost_param, base_estimator=ridge)
xgb = XGBRegressor(max_depth=xgb_param,base_estimator=ridge)



ridge.fit(X_train, y_train)#岭回归拟合
rf.fit(X_train, y_train)#随机森林的拟合,花费时间较多
bagging.fit(X_train, y_train)
adaboost.fit(X_train, y_train)
xgb.fit(X_train, y_train)



y_ridge = np.expm1(ridge.predict(X_test))#预测结果取e指数减1(log1p的逆过程)
y_rf = np.expm1(rf.predict(X_test))#随机森林的预测
y_bagging = np.expm1(bagging.predict(X_test))#预测结果取e指数减1(log1p的逆过程)
y_adaboost = np.expm1(adaboost.predict(X_test))#预测结果取e指数减1(log1p的逆过程)
y_xgb = np.expm1(xgb.predict(X_test))#预测结果取e指数减1(log1p的逆过程)

#y_final = y_xgb;
y_final = (y_ridge + y_rf + y_bagging +y_adaboost + y_xgb) / 5#直接求平均值,也算是一个模型的融合吧
submission_df = pd.DataFrame(data= {'Id' : test_df.index, 'SalePrice': y_final})
#构造提交文档
submission_df.head(10)
submission_df.to_csv('sub_result.csv',index=False,sep=',')#保存

实验结果与改进思路

在机器能承受的范围内,我不断进行调参和提交结果。最后一次提交的结果如下:

这里写图片描述

排名976,大概在前20%。

改进的方向大概可以是选取更多不太相关的模型,继续做集成。当然,如果有一个好的机器,我也是可以继续增加算法的某些参数,比如说增加随机森林中树的数量n_estimators等,但可想而知,这个提高应该不明显。
还有一个正经的Ensemble的方法是把这些model的预测结果作为新的input,再做一次预测来替代这里的取平均值。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陆嵩

有打赏才有动力,你懂的。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值