赞
踩
我从kaggle上找了一个问题,链接为房价预测。大概的意思就是,根据房子的一系列信息,包括面积,地段,环境等等,我来预测它的房价。它告诉了我一部分已知特征和房价的房子作为训练集,给出另外一部分只知特征的房子,让我预测出它们的房价。
下载的数据有三个csv文件,一个是训练数据,一个是测试数据,另外一个数据提交的样例。
训练数据为1460x80的一个表结构,有1460个观测,79个特征。第一列为索引,剩下79列为特征,最后一列为房价,如下图所示。
测试数据为1459x79的一个表结构,解释同训练数据,只是没有最后一列的房价。
有一个专门的文件解释每一列数据的含义,比如说MSSubClass列表示住房类型,不同数字表示不同的类型。
我细细研读和观察了一波数据。发现数据基本上分为数值类型和文本类型,还有一些缺失。数值类型除第一个特征MSSubClass只是一个编号外,其余都是连续型数据,这一列数据我应该视作文本来处理。文本型的数据特征项的取值域不是太大(这为我做dummpy提供了方便)。房价数据从肉眼上没看出什么,可能需要通过可视化找出一些规律。
拿到这样一个数据,为了将数据统一化处理,我首先训练数据中的价格列拿了出去,并将训练数据和测试数据按列纵向拼接在一起,以便进一步处理。合并后的数据规模为2919x79。
对于价格数据,我绘制出柱状图后发现有左偏,如图[pic2]我使用对数函数对其处理,使之分布更加均匀(极差变小的同时,左偏的数据取log处理,重心右挪)。对数变换即使用
对于一般的数值类型的特征,对于第一个特征MSSubClass,因为它的数值是表示住房类型,没有数值上的含义,所以将之转化为非数值型的对象类型,等同于文本。对于其他的数值特征,我进行标准化处理,即减去均值后除以标准差(
对于文本型数据(包括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的接口,调用其中的算法。岭回归的代码如下,惩罚系数
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,再做一次预测来替代这里的取平均值。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。