写这篇博客之前,我自己完成了这道题,kaggle的分数是0.14777,排名前百分之60,不是很满意,然后花了两天的空闲时间读了一份前百分之十,一份前百分之15的人的代码,比较了一下他们代码,发现了很多共同点,这些就是以后我需要注意的,总结来看学到了不少知识,在这里我按照一份代码讲解一下这个题,同时记录一下收获。
我看了他们的代码发现,他们都用了集成学习Stacking方法,这是我从来没有听说过的,深感惭愧。他们对于特征的处理,也就是特征工程部分做的很好,值得我学习。
我先讲一下那个排名前百分之十五的代码,(kaggle分数是0.11652,我看了下,大概是800多名,还算可以的分数和名次)
他主要分了这几个步骤:观察数据、数据清洗、特征工程、建立模型、训练优化模型。
是的,上面这句话就是废话,基本都是按照这个步骤走的,但是你跟别人的差距就出在这些很明确但是很难做好的步骤上,抓住细节,你就能登顶。
下面标红加粗的步骤是关键步骤
1.基本操作:
#import some necessary librairies
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import matplotlib.pyplot as plt # Matlab-style plotting
import seaborn as sns
color = sns.color_palette()
sns.set_style('darkgrid')
import warnings
def ignore_warn(*args, **kwargs):
pass
warnings.warn = ignore_warn #ignore annoying warning (from sklearn and seaborn)
from scipy import stats
from scipy.stats import norm, skew #for some statistics
pd.set_option('display.float_format', lambda x: '{:.3f}'.format(x)) #Limiting floats output to 3 decimal points
#from subprocess import check_output
#print(check_output(["ls", "../input"]).decode("utf8")) #check the files available in the directory
train = pd.read_csv('./data/train.csv')
test = pd.read_csv('./data/test.csv')
2.查看数据
#查看训练集、测试集大小
#check the numbers of samples and features
print("The train data size before dropping Id feature is : {} ".format(train.shape))
print("The test data size before dropping Id feature is : {} ".format(test.shape))
#Save the 'Id' column
train_ID = train['Id']
test_ID = test['Id']
#Now drop the 'Id' colum since it's unnecessary for the prediction process.
train.drop("Id", axis = 1, inplace = True)
test.drop("Id", axis = 1, inplace = True)
#check again the data size after dropping the 'Id' variable
print("\nThe train data size after dropping Id feature is : {} ".format(train.shape))
print("The test data size after dropping Id feature is : {} ".format(test.shape))
3.抛弃一些离群值:
#数据处理
#离群值处理
fig, ax = plt.subplots()
ax.scatter(x = train['GrLivArea'], y = train['SalePrice'])
plt.ylabel('SalePrice', fontsize=13)
plt.xlabel('GrLivArea', fontsize=13)
plt.show()
#我们可以看到图像右下角的两个点有着很大的GrLivArea,但相应的SalePrice却异常地低,我们有理由相信它们是离群值,要将其剔除。
#Deleting outliers
train = train.drop(train[(train['GrLivArea']>4000) & (train['SalePrice']<300000)].index)
#Check the graphic again
fig, ax = plt.subplots()
ax.scatter(train['GrLivArea'], train['SalePrice'])
plt.ylabel('SalePrice', fontsize=13)
plt.xlabel('GrLivArea', fontsize=13)
plt.show()
删除离群值不意味着所有的离群值你都给他删除喽,有一些噪声有助于提高模型的鲁棒性,从而在测试集方面表现的更好。
4.线性回归要求因变量SalePrice数据正态分布
#目标值分析
#我们画出SalePrice的分布图和QQ图(Quantile Quantile Plot)。这里简单说一下QQ图,它是由标准正态分布的分位数为横坐标,样本值为纵坐标的散点图。如果QQ图上的点在一条直线附近,则说明数据近似于正态分布,
# 且该直线的斜率为标准差,截距为均值。对于QQ图的详细介绍可以参考这篇文章:https://blog.csdn.net/hzwwpgmwy/article/details/79178485
def analyseAimVal(data):
sns.distplot(data , fit=norm)#distplot是画直方图#fit=norm拟合标准正态分布
# Get the fitted parameters used by the function
(mu, sigma) = norm.fit(data)#获取数据列正太分布的均值和标准差。
print( '\n mu = {:.2f} and sigma = {:.2f}\n'.format(mu, sigma))
#Now plot the distribution
plt.legend(['Normal dist. ($\mu=$ {:.2f} and $\sigma=$ {:.2f} )'.format(mu, sigma)],
loc='best')
plt.ylabel('Frequency')#出现率
plt.title('SalePrice distribution')
#Get also the QQ-plot
fig = plt.figure()
#如果两个分布相似,则该Q-Q图趋近于落在y=x线上。如果两分布线性相关,则点在Q-Q图上趋近于落在一条直线上,但不一定在y=x线上。
#下面的函数默认dist=norm,也就是说默认你传入的数据train['SalePrice']是与正态分布作比较的。
#这样的话QQ图鉴别样本数据是否近似于正态分布,只需看QQ图上的点是否近似地在一条直线附近,而且该直线的斜率为标准差,截距为均值.
res = stats.probplot(data,plot=plt)#函数默认值dist="norm",即默认与正态分布比较
plt.show()
analyseAimVal(train['SalePrice'])
#SalePrice的分布呈正偏态,而线性回归模型要求因变量服从正态分布。我们对其做对数变换,让数据接近正态分布。
#We use the numpy fuction log1p which applies log(1+x) to all elements of the column
train["SalePrice"] = np.log1p(train["SalePrice"])
analyseAimVal(train["SalePrice"])
5.查看特征相关性
#特征相关性
#用相关性矩阵热图表现特征与目标值之间以及两两特征之间的相关程度,对特征的处理有指导意义。
#Correlation map to see how features are correlated with SalePrice
corrmat = train.corr()
plt.subplots(figsize=(12,9))
sns.heatmap(corrmat, vmax=0.9, square=True)
6.查看各个特征的数值缺失情况
#让我们首先将训练数据和测试数据连接在同一个数据中
ntrain = train.shape[0]
ntest = test.shape[0]
y_train = train.SalePrice.values
all_data = pd.concat((train, test)).reset_index(drop=True)
all_data.drop(['SalePrice'], axis=1, inplace=True)
print("all_data size is : {}".format(all_data.shape))
#各个特征的数据缺失情况
all_data_na = (all_data.isnull().sum() / len(all_data)) * 100
all_data_na = all_data_na.drop(all_data_na[all_data_na == 0].index).sort_values(ascending=False)[:30]
missing_data = pd.DataFrame({'Missing Ratio' :all_data_na})
missing_data.head(20)
#画图展示缺失情况
f, ax = plt.subplots(figsize=(15, 12))
plt.xticks(rotation='90')
sns.barplot(x=all_data_na.index, y=all_data_na)
plt.xlabel('Features', fontsize=15)
plt.ylabel('Percent of missing values', fontsize=15)
plt.title('Percent missing data by feature', fontsize=15)
上面可以说除了正态化处理数据,都是一些对于数据的基本查看。下面进行数据清洗操作。
7.数据清洗之处理缺失值
学习人家对于缺失值的处理方法
#处理缺失值
#在data_description.txt中已有说明,一部分特征值的缺失是因为这些房子根本没有该项特征,对于这种情况我们统一用“None”或者“0”来填充。
all_data["PoolQC"] = all_data["PoolQC"].fillna("None")
all_data["MiscFeature"] = all_data["MiscFeature"].fillna("None")
all_data["Alley"] = all_data["Alley"].fillna("None")
all_data["Fence"] = all_data["Fence"].fillna("None")
all_data["FireplaceQu"] = all_data["FireplaceQu"].fillna("None")
all_data["MasVnrType"] = all_data["MasVnrType"].fillna("None")
all_data["MasVnrArea"] = all_data["MasVnrArea"].fillna(0)
for col in ('GarageType', 'GarageFinish', 'GarageQual', 'GarageCond'):
all_data[col] = all_data[col].fillna('None')
for col in ('GarageYrBlt', 'GarageArea', 'GarageCars'):
all_data[col] = all_data[col].fillna(0)
for col in ('BsmtFinSF1', 'BsmtFinSF2', 'BsmtUnfSF','TotalBsmtSF', 'BsmtFullBath', 'BsmtHalfBath'):
all_data[col] = all_data[col].fillna(0)
for col in ('BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2'):
all_data[col] = all_data[col].fillna('None')
#对于缺失较少的离散型特征,可以用众数填补缺失值。
all_data['MSZoning'] = all_data['MSZoning'].fillna(all_data['MSZoning'].mode()[0])
all_data['Electrical'] = all_data['Electrical'].fillna(all_data['Electrical'].mode()[0])
all_data['KitchenQual'] = all_data['KitchenQual'].fillna(all_data['KitchenQual'].mode()[0])
all_data['Exterior1st'] = all_data['Exterior1st'].fillna(all_data['Exterior1st'].mode()[0])
all_data['Exterior2nd'] = all_data['Exterior2nd'].fillna(all_data['Exterior2nd'].mode()[0])
all_data['SaleType'] = all_data['SaleType'].fillna(all_data['SaleType'].mode()[0])
#对于LotFrontage项,由于每个Neighborhood的房子的LotFrontage很可能是比较相近的,所以我们可以用各个房子所在Neighborhood的LotFrontage的中位数作为填充值。
#Group by neighborhood and fill in missing value by the median LotFrontage of all the neighborhood
all_data["LotFrontage"] = all_data.groupby("Neighborhood")["LotFrontage"].transform(
lambda x: x.fillna(x.median()))
#data_description.txt中还提到过,Functional默认是“Typ”。
all_data["Functional"] = all_data["Functional"].fillna("Typ")
#Utilities特征有两个缺失值,且只有一个样本是“NoSeWa”,除此之外全部都是“AllPub”,因此该项特征的方差非常小,我们可以直接将其删去。
all_data = all_data.drop(['Utilities'], axis=1)
#最后确认缺失值是否已全部处理完毕:
print(all_data.isnull().sum().max())
8.特征工程
有一些特征表面上是数值型的,其实只是表示不同类别,没有大小的意义,所以将其转化一下,反过来同理。
#进一步挖掘特征
#我们注意到有些特征虽然是数值型的,但其实表征的只是不同类别,其数值的大小并没有实际意义,因此我们将其转化为类别特征
all_data['MSSubClass'] = all_data['MSSubClass'].astype(str)
all_data['YrSold'] = all_data['YrSold'].astype(str)
all_data['MoSold'] = all_data['MoSold'].astype(str)
#反过来,有些类别特征实际上有高低好坏之分,这些特征的质量越高,就可能在一定程度导致房价越高。我们将这些特征的类别映射成有大小的数字,以此来表征这种潜在的偏序关系。
all_data['FireplaceQu'] = all_data['FireplaceQu'].map({'Ex': 5, 'Gd': 4, 'TA': 3, 'Fa': 2, 'Po': 1, 'None': 0})
all_data['GarageQual'] = all_data['GarageQual'].map({'Ex': 5, 'Gd': 4, 'TA': 3, 'Fa': 2, 'Po': 1, 'None': 0})
all_data['GarageCond'] = all_data['GarageCond'].map({'Ex': 5, 'Gd': 4, 'TA': 3, 'Fa': 2, 'Po': 1, 'None': 0})
all_data['GarageFinish'] = all_data['GarageFinish'].map({'Fin': 3, 'RFn': 2, 'Unf': 1, 'None': 0})
all_data['BsmtQual'] = all_data['BsmtQual'].map({'Ex': 5, 'Gd': 4, 'TA': 3, 'Fa': 2, 'Po': 1, 'None': 0})
all_data['BsmtCond'] = all_data['BsmtCond'].map({'Ex': 5, 'Gd': 4, 'TA': 3, 'Fa': 2, 'Po': 1, 'None': 0})
all_data['BsmtExposure'] = all_data['BsmtExposure'].map({'Gd': 4, 'Av': 3, 'Mn': 2, 'No': 1, 'None': 0})
all_data['BsmtFinType1'] = all_data['BsmtFinType1'].map({'GLQ': 6, 'ALQ': 5, 'BLQ': 4, 'Rec': 3, 'LwQ': 2, 'Unf': 1, 'None': 0})
all_data['BsmtFinType2'] = all_data['BsmtFinType2'].map({'GLQ': 6, 'ALQ': 5, 'BLQ': 4, 'Rec': 3, 'LwQ': 2, 'Unf': 1, 'None': 0})
all_data['ExterQual'] = all_data['ExterQual'].map({'Ex': 5, 'Gd': 4, 'TA': 3, 'Fa': 2, 'Po': 1, 'None': 0})
all_data['ExterCond'] = all_data['ExterCond'].map({'Ex': 5, 'Gd': 4, 'TA': 3, 'Fa': 2, 'Po': 1, 'None': 0})
all_data['HeatingQC'] = all_data['HeatingQC'].map({'Ex': 5, 'Gd': 4, 'TA': 3, 'Fa': 2, 'Po': 1, 'None': 0})
all_data['PoolQC'] = all_data['PoolQC'].map({'Ex': 5, 'Gd': 4, 'TA': 3, 'Fa': 2, 'Po': 1, 'None': 0})
all_data['KitchenQual'] = all_data['KitchenQual'].map({'Ex': 5, 'Gd': 4, 'TA': 3, 'Fa': 2, 'Po': 1, 'None': 0})
all_data['Functional'] = all_data['Functional'].map({'Typ': 8, 'Min1': 7, 'Min2': 6, 'Mod': 5, 'Maj1': 4, 'Maj2': 3, 'Sev': 2, 'Sal': 1, 'None': 0})
all_data['Fence'] = all_data['Fence'].map({'GdPrv': 4, 'MnPrv': 3, 'GdWo': 2, 'MnWw': 1, 'None': 0})
all_data['LandSlope'] = all_data['LandSlope'].map({'Gtl': 3, 'Mod': 2, 'Sev': 1, 'None': 0})
all_data['LotShape'] = all_data['LotShape'].map({'Reg': 4, 'IR1': 3, 'IR2': 2, 'IR3': 1, 'None': 0})
all_data['PavedDrive'] = all_data['PavedDrive'].map({'Y': 3, 'P': 2, 'N': 1, 'None': 0})
all_data['Street'] = all_data['Street'].map({'Pave': 2, 'Grvl': 1, 'None': 0})
all_data['Alley'] = all_data['Alley'].map({'Pave': 2, 'Grvl': 1, 'None': 0})
all_data['CentralAir'] = all_data['CentralAir'].map({'Y': 1, 'N': 0})
9.创造特征,这一块是我有待加强的
#利用一些重要的特征构造更多的特征:
all_data['TotalSF'] = all_data['TotalBsmtSF'] + all_data['1stFlrSF'] + all_data['2ndFlrSF']
all_data['OverallQual_TotalSF'] = all_data['OverallQual'] * all_data['TotalSF']
all_data['OverallQual_GrLivArea'] = all_data['OverallQual'] * all_data['GrLivArea']
all_data['OverallQual_TotRmsAbvGrd'] = all_data['OverallQual'] * all_data['TotRmsAbvGrd']
all_data['GarageArea_YearBuilt'] = all_data['GarageArea'] + all_data['YearBuilt']
10.Box-cox变换 :关于这一步也是我从未接触过的,具体怎么用感觉还是有些模糊,以后具体情况看别人代码学习一下吧
#对于数值型特征,我们希望它们尽量服从正态分布,也就是不希望这些特征出现正负偏态。
#那么我们先来计算一下各个特征的偏度:
#取出数值型特征
numeric_feats = all_data.dtypes[all_data.dtypes != "object"].index
# Check the skew of all numerical features
skewed_feats = all_data[numeric_feats].apply(lambda x: skew(x.dropna())).sort_values(ascending=False)#降序
skewness = pd.DataFrame({'Skew': skewed_feats})
print(skewness.head(10))
skewness = skewness[abs(skewness['Skew']) > 0.75]
print("There are {} skewed numerical features to Box Cox transform".format(skewness.shape[0]))
from scipy.special import boxcox1p
skewed_features = skewness.index
lam = 0.15
for feat in skewed_features:
all_data[feat] = boxcox1p(all_data[feat], lam)#用来降低X的skewness值,达到接近正态分布的目的
Box-cox可以改善数据的正态性、对称性和方差相等性。
11.独热编码,老生常谈了
#独热编码
#对于类别特征,我们将其转化为独热编码,这样既解决了模型不好处理属性数据的问题,在一定程度上也起到了扩充特征的作用。
all_data = pd.get_dummies(all_data)
print(all_data.shape)
train = all_data[:ntrain]
test = all_data[ntrain:]
12建立模型
这里加了一个评价模型,其实就是说不用原来的评价方式,而是用RMSE指标来为每个模型打分。
#建立模型
from sklearn.linear_model import ElasticNet, Lasso
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.kernel_ridge import KernelRidge
from sklearn.preprocessing import RobustScaler
from sklearn.base import BaseEstimator, TransformerMixin, RegressorMixin, clone
from sklearn.model_selection import KFold, cross_val_score
from sklearn.metrics import mean_squared_error
import xgboost as xgb
import lightgbm as lgb
#标准化
#有些时候,数据集中存在离群点,用Z-Score进行标准化,但是结果不理想,因为离群点在标准化后丧失了利群#特性。RobustScaler针对离群点做标准化处理,该方法对数据中心化的数据的缩放健壮性有更强的参数控制能#力。
scaler = RobustScaler()
train = scaler.fit_transform(train)
test = scaler.transform(test)
#评价函数
#先定义一个评价函数。我们采用5折交叉验证。与比赛的评价标准一致,我们用Root-Mean-Squared-Error (RMSE)来为每个模型打分。
#Validation function
n_folds = 5
#定义一个评价函数:
def rmsle_cv(model):
kf = KFold(n_folds, shuffle=True, random_state=42).get_n_splits(train)
rmse= np.sqrt(-cross_val_score(model, train, y_train, scoring="neg_mean_squared_error", cv = kf))
return(rmse)
#基本模型
lasso = Lasso(alpha=0.0005, random_state=1)
#弹性网络
ENet = ElasticNet(alpha=0.0005, l1_ratio=.9, random_state=3)
#核岭回归
KRR = KernelRidge(alpha=0.6, kernel='polynomial', degree=2, coef0=2.5)
#梯度提升回归
GBoost = GradientBoostingRegressor(n_estimators=1000, learning_rate=0.05,
max_depth=4, max_features='sqrt',
min_samples_leaf=15, min_samples_split=10,
loss='huber', random_state =5)
#XGBoost
model_xgb = xgb.XGBRegressor(colsample_bytree=0.5, gamma=0.05,
learning_rate=0.05, max_depth=3,
min_child_weight=1.8, n_estimators=1000,
reg_alpha=0.5, reg_lambda=0.8,
subsample=0.5, silent=1,
random_state =7, nthread = -1)
#LightGBM
model_lgb = lgb.LGBMRegressor(objective='regression',num_leaves=5,
learning_rate=0.05, n_estimators=1000,
max_bin = 55, bagging_fraction = 0.8,
bagging_freq = 5, feature_fraction = 0.2,
feature_fraction_seed=9, bagging_seed=9,
min_data_in_leaf =6, min_sum_hessian_in_leaf = 11)
#看看他们的表现
score = rmsle_cv(lasso)
print("\nLasso score: {:.4f} ({:.4f})\n".format(score.mean(), score.std()))#std标准差
score = rmsle_cv(ENet)
print("ElasticNet score: {:.4f} ({:.4f})\n".format(score.mean(), score.std()))
score = rmsle_cv(KRR)
print("Kernel Ridge score: {:.4f} ({:.4f})\n".format(score.mean(), score.std()))
score = rmsle_cv(GBoost)
print("Gradient Boosting score: {:.4f} ({:.4f})\n".format(score.mean(), score.std()))
score = rmsle_cv(model_xgb)
print("Xgboost score: {:.4f} ({:.4f})\n".format(score.mean(), score.std()))
score = rmsle_cv(model_lgb)
print("LGBM score: {:.4f} ({:.4f})\n" .format(score.mean(), score.std()))
13.Stacking 机器学习的大杀器出来了——集成学习
#集成学习往往能进一步提高模型的准确性,Stacking是其中一种效果颇好的方法,简单来说就是学习各个基本模型的预测值来预测最终的结果。详细步骤可参考:https://www.jianshu.com/p/59313f43916f
#这里我们用ENet、KRR和GBoost作为第一层学习器,用Lasso作为第二层学习器:
class StackingAveragedModels(BaseEstimator, RegressorMixin, TransformerMixin):
def __init__(self, base_models, meta_model, n_folds=5):
self.base_models = base_models
self.meta_model = meta_model
self.n_folds = n_folds
# We again fit the data on clones of the original models
def fit(self, X, y):
self.base_models_ = [list() for x in self.base_models]
self.meta_model_ = clone(self.meta_model)
kfold = KFold(n_splits=self.n_folds, shuffle=True, random_state=156)
# Train cloned base models then create out-of-fold predictions
# that are needed to train the cloned meta-model
out_of_fold_predictions = np.zeros((X.shape[0], len(self.base_models)))
for i, model in enumerate(self.base_models):
for train_index, holdout_index in kfold.split(X, y):
instance = clone(model)
instance.fit(X[train_index], y[train_index])
self.base_models_[i].append(instance)
y_pred = instance.predict(X[holdout_index])
out_of_fold_predictions[holdout_index, i] = y_pred
# Now train the cloned meta-model using the out-of-fold predictions as new feature
self.meta_model_.fit(out_of_fold_predictions, y)
return self
# Do the predictions of all base models on the test data and use the averaged predictions as
# meta-features for the final prediction which is done by the meta-model
def predict(self, X):
meta_features = np.column_stack([
np.column_stack([model.predict(X) for model in base_models]).mean(axis=1)
for base_models in self.base_models_])
return self.meta_model_.predict(meta_features)
#Stacking的交叉验证评分:
stacked_averaged_models = StackingAveragedModels(base_models = (ENet, GBoost, KRR),
meta_model = lasso)
score = rmsle_cv(stacked_averaged_models)
print("Stacking Averaged models score: {:.4f} ({:.4f})".format(score.mean(), score.std()))
我要说一说它的思想:对于一种模型(如GBoost),每次从全体训练数据中取一部分用于训练(代码中的for train_index, holdout_index in kfold.split(X, y)),将这次训练得到的模型保存起来(代码中的self.base_models_[i].append(instance)),同时可以对剩余的数据进行预测(代码中的y_pred = instance.predict(X[holdout_index])),然后再换另一种模型,重复这个分步训练过程。最后我们得到的是一个[X.shape[0],len(模型集)]=[样本数,模型数],也就是说我们用传入的每种模型对所有的样本都预测了一遍。这是fit方法的思路,在对于predict方法,我们要把fit保存的模型拿出来都预测predict一遍然后取平均值。这里涉及到一个np.column_stack方法,这个方法网上很多人说的都不全,我在另一篇博客里写了,大家可以移步。
np.column_stack([model.predict(X) for model in base_models]).mean(axis=1)得到了什么?一步一步看:
[model.predict(X) for model in base_models]得到的是
[
[x11,x12,x13,……],#模型x的第一次训练保存的模型(即self.base_models_[i].append(instance)添加的这个模型)对所有样本(即X)进行预测的结果
[x21,x22,x23……],#模型x的第二次训练保存的模型(即self.base_models_[i].append(instance)添加的这个模型)对所有样本(即X)进行预测的结果
]
np.column_stack([model.predict(X) for model in base_models])得到的是对上面的按照列进行重新组合结果:
[
[x11,x21,x31,……],#模型x的第1,2,3……次训练保存的模型对样本1进行预测的结果(具体模型经过了多少次训练呢?关于 for train_index, holdout_index in kfold.split(X, y):,kfold中的n_splits=几就训练多少次,就像交叉验证)
[x12,x22,x32……],#模型x的第1,2,3……次训练保存的模型对样本2进行预测的结果
]
np.column_stack([model.predict(X) for model in base_models]).mean(axis=1)因为axis=1所以得到对于列的平均:
[
x11,x21,x31,……的平均,#即对样本1的预测平均
x12,x22,x32……的平均,#即对样本2的预测平均
……
](是个一位数组哦~)
也就是说np.column_stack([model.predict(X) for model in base_models]).mean(axis=1)得到了模型x的所有训练模型对于每个样本预测结果的平均值。
完了吗?别忘了,外面还套了一层np.column_stack,我们这里只是得到了关于模型x的,还有其他几个模型呢!说到这里可能有人已经蒙了,我们看一下self.base_models_ = [list() for x in self.base_models]
self.base_models=[[],[],[]]#三个模型,经过fit方法之后每个模型列表里多了每次训练得到的模型
变成了
[
[模型x一次训练的模型,模型x两次训练的模型,模型x三次训练的模型……],
[模型y一次训练的模型,模型y两次训练的模型,模型y三次训练的模型……],
[模型z一次训练的模型,模型z两次训练的模型,模型z三次训练的模型……]
]
继续
meta_features = np.column_stack([
np.column_stack([model.predict(X) for model in base_models]).mean(axis=1)
for base_models in self.base_models_])
得到的就是
[
[x1,y1,z1……],#模型x,y,z……分别对样本1的平均预测结果组成的列表
[x2,y2,z2……],#模型x,y,z……分别对样本2的平均预测结果组成的列表
……
]
矩阵的尺寸就是之前的那个[样本数,模型数]
关于集成学习还有很多有用的知识,抽空一定要看一看。
14最终模型
#建立最终模型
#我们将XGBoost、LightGBM和StackedRegressor以加权平均的方式融合在一起,建立最终的预测模型。
#先定义一个评价函数:
def rmsle(y, y_pred):
return np.sqrt(mean_squared_error(y, y_pred))
#用整个训练集训练模型,预测测试集的房价,并给出模型在训练集上的评分。
#StackedRegressor
stacked_averaged_models.fit(train, y_train)
stacked_train_pred = stacked_averaged_models.predict(train)
stacked_pred = np.expm1(stacked_averaged_models.predict(test))#np.loglp计算加一后的对数,其逆运算是np.expm1;
print(rmsle(y_train, stacked_train_pred))
#XGBoost
model_xgb.fit(train, y_train)
xgb_train_pred = model_xgb.predict(train)
xgb_pred = np.expm1(model_xgb.predict(test))
print(rmsle(y_train, xgb_train_pred))
#LightGBM
model_lgb.fit(train, y_train)
lgb_train_pred = model_lgb.predict(train)
lgb_pred = np.expm1(model_lgb.predict(test))
print(rmsle(y_train, lgb_train_pred))
#融合模型的评分:
print('RMSLE score on train data:')
print(rmsle(y_train,stacked_train_pred*0.70 + xgb_train_pred*0.15 + lgb_train_pred*0.15))
#预测
ensemble = stacked_pred*0.70 + xgb_pred*0.15 + lgb_pred*0.15
#生成提交文件
sub = pd.DataFrame()
sub['Id'] = test_ID
sub['SalePrice'] = ensemble
sub.to_csv('submission3.csv', index=False)
#kaggle:0.11652
这里又重新进行了一次加权平均,额,虽然我感觉stacking之后的模型就已经够牛逼的了,不太清楚为什么还要加权平均。
上面代码中还有一个细节不知道大家注意到了没有。np.expm1,我们为什么要用np.log1p的逆运算?因为我们中间对于输出y_train,也就是train.SalePrice做了一次np.log1p使输出其趋于正态分布,但是原来的房价可没有加对数操作,这样预测出来的结果自然也是趋于正态分布的,我们自然要反回去,不然相当于对于预测值取了一次对数。由于使用的log1p()对数据进行了压缩,最后需要将预测出的平滑数据进行一个还原,而还原过程就是log1p的逆运算expm1.
kaggle的提交结果是0.11652,还可以了。
数据处理确实是一个很复杂的过程,我之前一直没有意识到,好像正态分布对于那些机器学习的算法有一些特殊的意义。