当前位置: 首页 > 科技观察

用Python做一个房价预测工具!

时间:2023-03-17 17:42:23 科技观察

大家好。这是一个房价预测的案例,来自Kaggle网站,是很多算法初学者的第一个比赛题目。这个案例对于理解机器学习问题有一个完整的流程,包括EDA、特征工程、模型训练、模型融合等,房价预测流程跟着我一起来了解这个案例。没有冗长的文字,没有冗余的代码,只有通俗的解释。1.EDA探索性数据分析(EDA)的目的是让我们对数据集有一个全面的了解。这一步我们探索的内容如下:EDA内容1.1输入数据集train=pd.read_csv('./data/train.csv')test=pd.read_csv('./data/test.csv')trainingsampletrain和test分别是训练集和测试集,有1460个样本和80个特征。SalePrice列表示房价,是我们要预测的价格。1.2房价分布由于我们的任务是预测房价,所以数据集中的核心焦点是销售价格(SalePrice)列的值分布。sns.distplot(火车['SalePrice']);房价的数值分布从图中可以看出,SalePrice这一列的峰值比较陡峭,而且峰值偏向左侧。也可以直接调用skew()和kurt()函数来计算SalePrice具体的偏度和峰度值。对于偏度和峰度都比较大的情况,建议用log()对SalePrice列进行平滑处理。1.3与房价相关的特征了解了SalePrice的分布后,我们可以计算出80个特征与SalePrice的相关性。关注与SalePrice最相关的10个特征。#计算列之间的相关性corrmat=train.corr()#取top10k=10cols=corrmat.nlargest(k,'SalePrice')['SalePrice'].index#绘制cm=np.corrcoef(train[cols].values.T)sns.set(font_scale=1.25)hm=sns.heatmap(cm,cbar=True,annot=True,square=True,fmt='.2f',annot_kws={'size':10},yticklabels=cols.values,xticklabels=cols.values)plt.show()与SalePriceOverallQual(房屋材料和饰面)、GrLivArea(地上生活区)、GarageCars(车库容量)和TotalBsmtSF(地下室面积)高度相关的特征与SalePrice有很强的相关性。后面做特征工程的时候也会重点关注这些特征。1.4剔除异常样本由于数据集的样本量较小,异常样本不利于我们后期训练模型。因此,需要计算每个数值特征的离群值,剔除离群值最多的样本。#获取数值特征numeric_features=train.dtypes[train.dtypes!='object'].index#计算每个特征的离群样本forfeatureinnumeric_features:outs=detect_outliers(train[feature],train['SalePrice'],top=5,plot=False)all_outliers.extend(outs)#输出异常值最多的样本print(Counter(all_outliers).most_common())#消除异常值train=train.drop(train.index[outliers])detect_outliers()是一个自定义函数,它使用sklearn库的LocalOutlierFactor算法来计算离群值。至此,EDA完成。最后将训练集和测试集结合起来进行下面的特征工程。y=train.SalePrice.reset_index(drop=True)train_features=train.drop(['SalePrice'],axis=1)test_features=testfeatures=pd.concat([train_features,test_features]).reset_index(drop=True)特征训练集和测试集的特征结合在一起,就是我们下面要处理的数据。2.特征工程特征工程2.1标定特征类型MSSubClass(房屋类型)、YrSold(销售年份)和MoSold(销售月份)是类别特征,但都是用数字表示,需要转化为文本特征。features['MSSubClass']=features['MSSubClass'].apply(str)features['YrSold']=features['YrSold'].astype(str)features['MoSold']=features['MoSold'].astype(str)2.2填充特征缺失值缺失值的填充没有统一的标准,需要根据不同的特征来决定如何填充。#Functional:文档提供了典型值Typfeatures['Functional']=features['Functional'].fillna('Typ')#Typ为典型值#分组填充需要按照相似的特征进行分组,取modeormedian#MSZoning(房屋面积)根据MSSubClass(房屋)的类型进行分组填充模式features['MSZoning']=features.groupby('MSSubClass')['MSZoning'].transform(lambdax:x.fillna(x.mode()[0]))#LotFrontage(接收示例)按Neighborhood分组以填充中位数features['LotFrontage']=features.groupby('Neighborhood')['LotFrontage']。transform(lambdax:x.fillna(x.median()))#车库相关的数值特征,空代表什么,用0补空值。forcolin('GarageYrBlt','GarageArea','GarageCars'):features[col]=features[col].fillna(0)2.3偏度校正类似于探索SalePrice列,平滑具有高偏度的特征。#skew()方法,计算特征的偏度(skewness)。skew_features=features[numeric_features].apply(lambdax:skew(x)).sort_values(ascending=False)#取偏度大于0.15的特征high_skew=skew_features[skew_features>0.15]skew_index=high_skew.index#处理高偏度特征,将其转化为正态分布,或者对skew_index中的i使用简单的对数变换:features[i]=boxcox1p(features[i],boxcox_normmax(features[i]+1))2.4特征删除和添加对于几乎所有缺失值,或者单值占比高(99.94%)的特征,可以直接删除。features=features.drop(['Utilities','Street','PoolQC',],axis=1)同时可以融合多个特征,生成新的特征。有时模型很难学习特征之间的关系。手动特征融合可以降低模型学习的难度,提高效果。#合并原始建造日期和改造日期features['YrBltAndRemod']=features['YearBuilt']+features['YearRemodAdd']#合并地下室区域、1层、2层区域特征['TotalSF']=features['TotalBsmtSF']+features['1stFlrSF']+features['2ndFlrSF']可以发现我们融合的特征都是和SalePrice强相关的。最后对特征进行简化,对分布单调的特征进行01处理(例如:100条数据中有99条的值为0.9,另一条为0.1)。features['haspool']=features['PoolArea'].apply(lambdax:1ifx>0else0)features['has2ndfloor']=features['2ndFlrSF'].apply(lambdax:1ifx>0else0)2.6生成最终训练数据至此,特征工程完成。我们需要从特征中分离出训练集和测试集来构建最终的训练数据。X=features.iloc[:len(y),:]X_sub=features.iloc[len(y):,:]X=np.array(X.copy())y=np.array(y)X_sub=np.array(X_sub.copy())3.模型训练因为SalePrice是数值型的连续的,所以需要训练一个回归模型。3.1单一模型首先以岭回归(Ridge)为例,构建k折交叉验证模型。从sklearn.linear_model导入RidgeCV从sklearn.pipeline导入make_pipeline从sklearn.model_selection导入KFoldkfolds=KFold(n_splits=10,shuffle=True,random_state=42)alphas_alt=[14.5,14.6,14.7,14.8,14.9,15,15.2,1515.3,15.4,15.5]ridge=make_pipeline(RobustScaler(),RidgeCV(alphas=alphas_alt,cv=kfolds))岭回归模型有一个超参数alpha,RidgeCV的参数名是alphas,表示输入一个超参数alpha数组.在拟合模型时,会从alpha数组中选择一个性能较好的值。由于现在只有一个模型,所以无法确定岭回归是否是最好的模型。所以我们可以找一些出镜率高的机型多尝试一下。#lassolasso=make_pipeline(RobustScaler(),LassoCV(max_iter=1e7,alphas=alphas2,random_state=42,cv=kfolds))#elasticnetelasticnet=make_pipeline(RobustScaler(),ElasticNetCV(max_iter=1e7,alphas=e_alphas,cv=kfolds,l1_ratio=e_l1ratio))#svmsvr=make_pipeline(RobustScaler(),SVR(C=20,epsilon=0.008,gamma=0.0003,))#GradientBoosting(扩展到一阶导数)gbr=GradientBoostingRegressor(...)#lightgbmlightgbm=LGBMRegressor(...)#xgboost(展开为二阶导数)xgboost=XGBRegressor(...)对于多个模型,我们可以定义一个评分函数来对模型进行评分。#模型评分函数defcv_rmse(model,X=X):rmse=np.sqrt(-cross_val_score(model,X,y,scoring="neg_mean_squared_error",cv=kfolds))return(rmse)以岭回归为例,计算模型得分。score=cv_rmse(ridge)print("Ridge分数:{:.4f}({:.4f})\n".format(score.mean(),score.std()),datetime.now(),)#0.1024运行其他模型,发现分数都差不多。这时候我们就可以选择一个模型,进行拟合、预测,并提交训练结果。仍以岭回归为例#训练模型ridge.fit(X,y)#模型预测submission.iloc[:,1]=np.floor(np.expm1(ridge.predict(X_sub)))#输出测试结果submission=pd.read_csv("./data/sample_submission.csv")submission.to_csv("submission_single.csv",index=False)submission_single.csv是岭回归预测的房价,我们可以把这个结果上传到Kaggle网站查看结果分数和排名。3.2Modelfusion-stacking有时候为了发挥多个模型的作用,我们会融合多个模型。这种方法也称为集成学习。堆叠是一种常见的集成学习方法。简单来说,它定义了一个元模型,其他模型的输出作为元模型的输入特征,元模型的输出作为最终的预测结果。对于堆叠,我们使用mlextend库中的StackingCVRegressor模块来堆叠模型。stack_gen=StackingCVRegressor(regressors=(ridge,lasso,elasticnet,gbr,xgboost,lightgbm),meta_regressor=xgboost,use_features_in_secondary=True)训练和预测的过程同上,这里不再赘述。3.3ModelFusion-LinearFusion多模型线性融合的思路很简单。每个模型都分配了一个权重(权重和=1),最终的预测结果是每个模型的加权平均。#训练单个模型ridge_model_full_data=ridge.fit(X,y)lasso_model_full_data=lasso.fit(X,y)elastic_model_full_data=elasticnet.fit(X,y)gbr_model_full_data=gbr.fit(X,y)xgb_model_full_data=xgboost.fit(X,y)lgb_model_full_data=lightgbm.fit(X,y)svr_model_full_data=svr.fit(X,y)models=[ridge_model_full_data,lasso_model_full_data,elastic_model_full_data,gbr_model_full_data,xgb_model_full_data,lgb_model_full_data,svr_model_full_data,stack_gen_model]#分配模型权重public_coefs=[0.1,0.1,0.1,0.1,0.15,0.1,0.1,0.25]#线性融合,取加权平均deflinear_blend_models_predict(data_x,models,coefs,bias):tmp=[model.predict(data_x)formodelinmodels]tmp=[c*dforc,dinzip(coefs,tmp)]pres=np.array(tmp).swapaxes(0,1)pres=np.sum(pres,axis=1)在这里返回pres,价格我们已经完成了预测案例的解释。大家可以自己跑一下,看看不同方式训练出来的模型的效果。回顾整个案例,我们会发现我们在数据预处理和特征工程上花了不少心思。虽然机器学习问题模型的原理很难学,但在实际过程中往往最费心思的还是特征工程。