当前位置: 代码迷 >> 综合 >> Kaggle - House_Price初级、进阶篇幅
  详细解决方案

Kaggle - House_Price初级、进阶篇幅

热度:49   发布时间:2023-11-25 05:13:50.0

Step 1: 检视源数据集 

import numpy as np
import pandas as pd

读入数据

  • 一般来说源数据的index那一栏没什么用,我们可以用来作为我们pandas dataframe的index。这样之后要是检索起来也省事儿。

  • 有人的地方就有鄙视链。跟知乎一样。Kaggle的也是个处处呵呵的危险地带。Kaggle上默认把数据放在input文件夹下。所以我们没事儿写个教程什么的,也可以依据这个convention来,显得自己很有逼格。。

  • 路径不要有中文,不要是/

train_df = pd.read_csv('F:\\PY\\train.csv', index_col=0)
test_df = pd.read_csv('F:\\PY\\test.csv', index_col=0)

检视源数据

train_df.head()

 这时候大概心里可以有数,哪些地方需要人为的处理一下,以做到源数据更加好被process。

Step 2: 合并数据

这么做主要是为了用DF进行数据预处理的时候更加方便。等所有的需要的预处理进行完之后,我们再把他们分隔开。

首先,SalePrice作为我们的训练目标,只会出现在训练集中,不会在测试集中(要不然你测试什么?)。所以,我们先把SalePrice这一列给拿出来,不让它碍事儿。

%matplotlib inline  魔法函数,“%matplotlib inline”就是模仿命令行来访问magic函数的在IPython中独有的形式。功能是可以内嵌绘图,并且可以省略掉plt.show()这一步。
pd.DataFrame        用pandas创建数据表:
prices.hist()       是直方图

我们先看一下SalePrice长什么样纸:

%matplotlib inline
prices = pd.DataFrame({"price":train_df["SalePrice"], "log(price + 1)":np.log1p(train_df["SalePrice"])})
prices.hist()

 

可见,label本身并不平滑。为了我们分类器的学习更加准确,我们会首先把label给“平滑化”(正态化)

这一步大部分同学会miss掉,导致自己的结果总是达不到一定标准。

这里我们使用最有逼格的log1p, 也就是 log(x+1),避免了复值的问题。

记住哟,如果我们这里把数据都给平滑化了,那么最后算结果的时候,要记得把预测到的平滑数据给变回去。

按照“怎么来的怎么去”原则,log1p()就需要expm1(); 同理,log()就需要exp(), ... etc.

y_train = np.log1p(train_df.pop('SalePrice'))

然后我们把剩下的部分合并起来

all_df = pd.concat((train_df, test_df), axis=0)

此刻,我们可以看到all_df就是我们合在一起的DF

all_df.shape

y_train则是SalePrice那一列

y_train.head()

Step 3: 变量转化

类似『特征工程』。就是把不方便处理或者不unify的数据给统一了。

正确化变量属性

首先,我们注意到,MSSubClass 的值其实应该是一个category,

但是Pandas是不会懂这些事儿的。使用DF的时候,这类数字符号会被默认记成数字。

这种东西就很有误导性,我们需要把它变回成string

all_df['MSSubClass'].dtypes

all_df['MSSubClass'] = all_df['MSSubClass'].astype(str)

变成str以后,做个统计,就很清楚了

all_df['MSSubClass'].value_counts()

把category的变量转变成numerical表达形式

当我们用numerical来表达categorical的时候,要注意,数字本身有大小的含义,所以乱用数字会给之后的模型学习带来麻烦。于是我们可以用One-Hot的方法来表达category。

pandas自带的get_dummies方法,可以帮你一键做到One-Hot。

pd.get_dummies(all_df['MSSubClass'], prefix='MSSubClass').head()

 

此刻MSSubClass被我们分成了16个column,每一个代表一个category。是就是1,不是就是0。

同理,我们把所有的category数据,都给One-Hot了

all_dummy_df = pd.get_dummies(all_df)
all_dummy_df.head()

他是自动的去找可以one-hot的值,关于不是的就不做相应变换了,如下,所以总共砍到303列

处理好numerical变量

就算是numerical的变量,也还会有一些小问题。

比如,有一些数据是缺失的:

all_dummy_df.isnull().sum().sort_values(ascending=False).head(10)

 

可以看到,缺失最多的column是LotFrontage

处理这些缺失的信息,得靠好好审题。一般来说,数据集的描述里会写的很清楚,这些缺失都代表着什么。当然,如果实在没有的话,也只能靠自己的『想当然』。。

在这里,我们用平均值来填满这些空缺。

mean_cols = all_dummy_df.mean()
mean_cols.head(10)

all_dummy_df = all_dummy_df.fillna(mean_cols)

看看是不是没有空缺了?

all_dummy_df.isnull().sum().sum()

标准化numerical数据

这一步并不是必要,但是得看你想要用的分类器是什么。一般来说,regression的分类器都比较傲娇,最好是把源数据给放在一个标准分布内。不要让数据间的差距太大。

这里,我们当然不需要把One-Hot的那些0/1数据给标准化。我们的目标应该是那些本来就是numerical的数据:

先来看看 哪些是numerical的:

numeric_cols = all_df.columns[all_df.dtypes != 'object']
numeric_cols

计算标准分布:(X-X')/s让我们的数据点更平滑,更便于计算。注意:我们这里也是可以继续使用Log的,我只是给大家展示一下多种“使数据平滑”的办法。
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

Step 4: 建立模型

把数据集分回 训练/测试集

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

Ridge Regression  岭回归

用Ridge Regression模型来跑一遍看看。(对于多因子的数据集,这种模型可以方便的把所有的var都无脑的放进去)

from sklearn.linear_model import Ridge
from sklearn.model_selection import cross_val_score

把pandas的DataFrame转换成numpy的Array,只是为了和sklearn更配 ,这一步不是很必要,只是把DF转化成Numpy Array,这跟Sklearn更加配

X_train = dummy_train_df.values
X_test = dummy_test_df.values

用Sklearn自带的cross validation方法来测试模型

剩下就是用sklearn的岭回归Ridge训练模型了,这个模型需要一个alpha超参,我们用交叉验证得到最优的参数值。

交叉验证的代码套路是固定的:

alphas = np.logspace(-3, 2, 50)
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))

 

alphas = np.logspace(-3, 2, 50)

 这段代码运行完毕后,我们有两个列表,一个是alphas列表,一个是test_scores列表。用这两个列表画一个折线图,我们就可以观察到在alpha取什么值的时候,误差最小。

import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(alphas, test_scores)
plt.title("Alpha vs CV Error");

 

                              

可见,大概alpha=10~20的时候,可以把score达到0.135左右。

岭回归(Ridge Regression)是在平方误差的基础上增加正则项

 

,

理解线性回归(三)——岭回归Ridge Regression  具体可看:https://blog.csdn.net/puqutogether/article/details/40863943

class sklearn.linear_model.Ridge(alpha=1.0, fit_intercept=True,normalize=False, copy_X=True, max_iter=None, tol=0.001, solver='auto')

alpha:上面提到的两项之间的权重;
fit_intercept:默认为true,数据可以拦截,没有中心化;
normalize:输入的样本特征归一化,默认false;
copy_X:复制或者重写;
max_iter:最大迭代次数;
tol: 控制求解的精度;
solver:求解器,有auto, svd, cholesky, sparse_cg, lsqr几种,一般我们选择auto,一些svd,cholesky也都是稀疏表示中常用的omp求解算法中的知识,大家有时间可以去了解。

Ridge函数会返回一个clf类,里面有很多的函数,一般我们用到的有:
clf.fit(X, y):输入训练样本数据X,和对应的标记y;
clf.predict(X):利用学习好的线性分类器,预测标记,一般在fit之后调用;
clf.corf_: 输入回归表示系数
 

Random Forest

同样的数据集,也可以用RandomForestRegressor来做回归。这个模型有两个超参数,最大特征个数、最大采样数量。

from sklearn.ensemble import RandomForestRegressor
max_features = [.1, .3, .5, .7, .9, .99]
test_scores = []
for max_feat in max_features:#n_estimators 代表要多少棵树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");

用RF的最优值达到了0.137。

通过交叉验证,我们可以找到两个模型的最佳超参数。

Step 5: Ensemble 

应该也可以叫做模型融合

这里我们用一个Stacking的思维来汲取两种或者多种模型的优点

首先,我们把最好的parameter拿出来,做成我们最终的model

简单的我们可以用bagging的方法,把两个模型最好测参数拿出来,组合成最终的model。每个模型都做个预测值,最终两个模型的预测值取平均。

ridge = Ridge(alpha=15)
rf = RandomForestRegressor(n_estimators=500, max_features=.3)
ridge.fit(X_train, y_train)
rf.fit(X_train, y_train)

上面提到了,因为最前面我们给label做了个log(1+x), 于是这里我们需要把predit的值给exp回去,并且减掉那个"1"

所以就是我们的expm1()函数。

y_ridge = np.expm1(ridge.predict(X_test))
y_rf = np.expm1(rf.predict(X_test))

 一个正经的Ensemble是把这群model的预测结果作为新的input,再做一次预测。这里我们简单的方法,就是直接『平均化』。

y_final = (y_ridge + y_rf) / 2

Step 6: 提交结果

submission_df = pd.DataFrame(data= {'Id' : test_df.index, 'SalePrice': y_final})

我们的submission大概长这样:

submission_df.head(10)

进阶优化 用高级的模型融合

一般来说,单个分类器的效果有效,我们会倾向于把N多的分类器合在一起,做一个综合分类器达到最好的效果。

sklearn中已经有现成框架,在sklearn.ensemble中,有bagging有Boosting。

Bagging和Bossting又都有回归和分类之分。

把bagging用在回归上,也是同时得到多个回归模型,比如这个案例中我们用Ridge做回归器,最后就是得到多个Ridge。

Bagging缺省的时候用的是DecisionTree,通过改变base_estimator参数的值就可以修改弱模型的类型了。

使用上面的ridge试一试看:

from sklearn.ensemble import BaggingRegressor
from sklearn.linear_model import Ridge
ridge = Ridge(alpha=15)
params = [1, 20, 30, 40, 50, 60, 70]
BaggingRegressor_test_scores = []
for param in params:BaggingRegressor_clf = BaggingRegressor(n_estimators=param, base_estimator=ridge)BaggingRegressor_test_score = np.sqrt(-cross_val_score(BaggingRegressor_clf, X_train, y_train, cv=15, scoring='neg_mean_squared_error'))BaggingRegressor_test_scores.append(np.mean(BaggingRegressor_test_score))
plt.plot(params, BaggingRegressor_test_scores)
plt.title("Bagging_n_estimator vs CV Error")

params = 50的时候呀  较好。

 

Boosting:增强学习原理 (Adaboost)

Boosting: Boosting比Bagging理论上更高级点,它也是揽来一把的分类器。但是把他们线性排列。下一个分类器把上一个分类器分类得不好的地方加上更高的权重,这样下一个分类器就能在这个部分学得更加“深刻”。


from sklearn.ensemble import AdaBoostRegressor
AdaBoostRegressor_params = [50,60,70,80,90,100,110,120,130,140]
AdaBoostRegressor_test_scores = []
for params in AdaBoostRegressor_params:AdaBoostRegressor_clf = AdaBoostRegressor(n_estimators=params, base_estimator=ridge)AdaBoostRegressor_test_score = np.sqrt(-cross_val_score(AdaBoostRegressor_clf, X_train, y_train, cv=15, scoring='neg_mean_squared_error'))AdaBoostRegressor_test_scores.append(np.mean(AdaBoostRegressor_test_score))

 

plt.plot(AdaBoostRegressor_params, AdaBoostRegressor_test_scores)
plt.title("AdaBoostRegressor_n_estimator vs CV Error")

 在 cv =15,在ridge(15)的情况下

得到AdaBoostRegressor_params是100时最佳。
 

在cv =10时

关于K-fold Cross Validation(K-折交叉验证,记为K-CV) k有比较好的值推荐吗?还是说有什么套路?

  1. 查看https://machinelearningmastery.com/k-fold-cross-validation/  从上面的结果看也是k=10时loss小些,且参数取120了。
  2. 交叉验证:https://blog.csdn.net/holybin/article/details/27185659

导出数据 

br = BaggingRegressor(n_estimators = 10, base_estimator = ridge) 
br.fit(X_train, y_train)
y_br = np.expm1(br.predict(X_test))
data = pd.DataFrame({'Id':test_df.index, 'SalePrice':y_br})
data.to_csv('example.csv')

大杀器,xgboost

from xgboost import XGBRegressor
XGBRegressor_params = [1,2,3,4,5,6,7,8,9,10]
XGBRegressor_test_scores=[]
for param in XGBRegressor_params :XGBRegressor_clf = XGBRegressor(max_depth=param)XGBRegressor_test_score = np.sqrt(-cross_val_score(XGBRegressor_clf,X_train,y_train,cv=10,scoring='neg_mean_squared_error'))XGBRegressor_test_scores.append(np.mean(XGBRegressor_test_score))

 

上图是cv=10的时候

效果应该是最好的!综上得到。

可以用xgboost,而且xgboost可以用一样的模式嵌入到sklearn中,代码套路完全一样
参考:https://www.jianshu.com/p/c58645406a3f

https://blog.csdn.net/sinat_15355869/article/details/79959170   Kaggle - House_Price进阶篇幅

https://www.cnblogs.com/pinard/p/6023000.html  用scikit-learn和pandas学习Ridge回归

https://blog.csdn.net/wushaowu2014/article/details/78305232  python中报错:SyntaxError: invalid character in identifier

https://zhuanlan.zhihu.com/p/26704531  【scikit-learn文档解析】集成方法 Ensemble Methods(下):AdaBoost,GBDT与投票分类器