前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >过关斩将打进Kaggle竞赛Top 0.3%,我是这样做的

过关斩将打进Kaggle竞赛Top 0.3%,我是这样做的

作者头像
AI科技大本营
发布2019-07-18 17:15:05
1.8K1
发布2019-07-18 17:15:05
举报

作者 | Lavanya Shukla

译者 | Monanfei

责编 | 夕颜

出品 | AI科技大本营(id:rgznai100)

导读:刚开始接触数据竞赛时,我们可能会被一些高大上的技术吓到。各界大佬云集,各种技术令人眼花缭乱,新手们就像蜉蝣一般渺小无助。今天本文就分享一下在 kaggle 的竞赛中,参赛者取得 top0.3% 的经验和技巧。让我们开始吧!

Top 0.3% 模型概览

赛题和目标

  • 数据集中的每一行都描述了某一房屋的特征
  • 在已知这些特征的条件下,预测每间房的销售价格
  • 预测价格对数和真实价格对数的RMSE(均方根误差)作为模型的评估指标。将RMSE转化为对数尺度,能够保证廉价房屋和高价房屋的预测误差,对模型分数的影响较为一致。

模型训练过程中的重要细节

  • 交叉验证:使用12-折交叉验证
  • 模型:在每次交叉验证中,同时训练七个模型(ridge, svr, gradient boosting, random forest, xgboost, lightgbm regressors)
  • Stacking 方法:使用 xgboot 训练了元 StackingCVRegressor 学习器
  • 模型融合:所有训练的模型都会在不同程度上过拟合,因此,为了做出最终的预测,将这些模型进行了融合,得到了鲁棒性更强的预测结果

模型性能

从下图可以看出,融合后的模型性能最好,RMSE 仅为 0.075,该融合模型用于最终预测。

In[1]:

代码语言:javascript
复制
from IPython.display import Image
Image("../input/kernel-files/model_training_advanced_regression.png")

Output[1]:

现在让我们正式开始吧!

In[2]:

代码语言:javascript
复制
# Essentials
import numpy as np
import pandas as pd
import datetime
import random
# Plots
import seaborn as sns
import matplotlib.pyplot as plt


# Models
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor, AdaBoostRegressor, BaggingRegressor
from sklearn.kernel_ridge import KernelRidge
from sklearn.linear_model import Ridge, RidgeCV
from sklearn.linear_model import ElasticNet, ElasticNetCV
from sklearn.svm import SVR
from mlxtend.regressor import StackingCVRegressor
import lightgbm as lgb
from lightgbm import LGBMRegressor
from xgboost import XGBRegressor


# Stats
from scipy.stats import skew, norm
from scipy.special import boxcox1p
from scipy.stats import boxcox_normmax


# Misc
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import KFold, cross_val_score
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import LabelEncoder
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import scale
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import RobustScaler
from sklearn.decomposition import PCA

pd.set_option('display.max_columns', None)

# Ignore useless warnings
import warnings
warnings.filterwarnings(action="ignore")
pd.options.display.max_seq_items = 8000
pd.options.display.max_rows = 8000

import os
print(os.listdir("../input/kernel-fi

Output[2]:

['model_training_advanced_regression.png']

In[3]:

代码语言:javascript
复制

Output[3]:

((1460, 81), (1459, 80))

EDA

目标

  • 数据集中的每一行都描述了某一间房的特征
  • 在已知这些特征的条件下,预测每间房的销售价格

对原始数据进行可视化

In[4]:

代码语言:javascript
复制

Output[5]:

SalePrice:目标值的特性探究

In[5]:

代码语言:javascript
复制

In[6]:

代码语言:javascript
复制

Skewness: 1.882876 Kurtosis: 6.536282 可用的特征:深入探索

数据可视化

In[7]:

代码语言:javascript
复制

探索这些特征以及 SalePrice 的相关性

In[8]:

代码语言:javascript
复制

Output[8]:

代码语言:javascript
复制
代码语言:javascript
复制
<matplotlib.axes._subplots.AxesSubplot at 0x7ff0e416e4e0>
代码语言:javascript
复制

选取部分特征,可视化它们和 SalePrice 的相关性

Input[9]:

代码语言:javascript
复制

Input[10]:

代码语言:javascript
复制

Input[11]:

代码语言:javascript
复制
.3, ylim=(0,800000));

Input[12]:

代码语言:javascript
复制
lim=(0,800000));

Input[13]:

代码语言:javascript
复制
ylim=(0,800000));

Input[14]:

代码语言:javascript
复制

Output[14]:

((1460, 80), (1459, 79)) 可视化 salePrice 的分布

Input[15]:

代码语言:javascript
复制

从上图中可以看出,SalePrice 有点向右边倾斜,由于大多数机器学习模型对非正态分布的数据的效果不佳,因此,我们对数据进行变换,修正这种倾斜:log(1+x)

Input[16]:

代码语言:javascript
复制

对 SalePrice 重新进行可视化

Input[17]:

代码语言:javascript
复制
sns.set_style("white")
sns.set_color_codes(palette='deep')
f, ax = plt.subplots(figsize=(8, 7))
#Check the new distribution
sns.distplot(train['SalePrice'] , fit=norm, color="b");


# Get the fitted parameters used by the function
(mu, sigma) = norm.fit(train['SalePrice'])
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')
ax.xaxis.grid(False)
ax.set(ylabel="Frequency")
ax.set(xlabel="SalePrice")
ax.set(title="SalePrice distribution")
sns.despine(trim=True, left=True)

plt.show

mu = 12.02 and sigma = 0.40

从图中可以看到,当前的 SalePrice 已经变成了正态分布

Input[18]:

代码语言:javascript
复制

Input[19]:

代码语言:javascript
复制
# Split features and labels
train_labels = train['SalePrice'].reset_index(drop=True)
train_features = train.drop(['SalePrice'], axis=1)
test_features = test
# Combine train and test features in order to apply the feature transformation pipeline to the entire dataset
all_features = pd.concat([train_features, test_features]).reset_index(drop=True)
all_features.shape

Input[19]:

(2917, 79)

填充缺失值

Input[20]:

代码语言:javascript
复制
# determine the threshold for missing values
def percent_missing(df):
    data = pd.DataFrame(df)
    df_cols = list(pd.DataFrame(data))
    dict_x = {}
    for i in range(0, len(df_cols)):
        dict_x.update({df_cols[i]: round(data[df_cols[i]].isnull().mean()*100,2)})
    
    return dict_x

missing = percent_missing(all_features)
df_miss = sorted(missing.items(), key=lambda x: x[1], reverse=True)
print('Percent of missing data')
df_miss[0:10]

Percent of missing data

Output[20]:

[('PoolQC', 99.69),

('MiscFeature', 96.4),

('Alley', 93.21),

('Fence', 80.43),

('FireplaceQu', 48.68),

('LotFrontage', 16.66),

('GarageYrBlt', 5.45),

('GarageFinish', 5.45),

('GarageQual', 5.45),

('GarageCond', 5.45)]

Input[21]:

代码语言:javascript
复制

接下来,我们将分别对每一列填充缺失值

Input[22]:

代码语言:javascript
复制

Input[23]:

代码语言:javascript
复制

Input[24]:

代码语言:javascript
复制

Output[14]:

Percent of missing data

[('MSSubClass', 0.0),

('MSZoning', 0.0),

('LotFrontage', 0.0),

('LotArea', 0.0),

('Street', 0.0),

('Alley', 0.0),

('LotShape', 0.0),

('LandContour', 0.0),

('Utilities', 0.0),

('LotConfig', 0.0)]

从上面的结果可以看到,所有缺失值已经填充完毕

调整分布倾斜的特征

Input[25]:

代码语言:javascript
复制

Input[26]:

代码语言:javascript
复制
# Create box plots for all numeric features
sns.set_style("white")
f, ax = plt.subplots(figsize=(8, 7))
ax.set_xscale("log")
ax = sns.boxplot(data=all_features[numeric] , orient="h", palette="Set1")
ax.xaxis.grid(False)
ax.set(ylabel="Feature names")
ax.set(xlabel="Numeric values")
ax.set(title="Numeric Distribution of Features")
sns.despine(trim=True, left=True)

Input[27]:

代码语言:javascript
复制

Output[27]:

There are 25 numerical features with Skew > 0.5 :

MiscVal 21.939672

PoolArea 17.688664

LotArea 13.109495

LowQualFinSF 12.084539

3SsnPorch 11.372080

KitchenAbvGr 4.300550

BsmtFinSF2 4.144503

EnclosedPorch 4.002344

ScreenPorch 3.945101

BsmtHalfBath 3.929996

dtype: float64

使用 scipy 的函数 boxcox1来进行 Box-Cox 转换,将数据正态化

代码语言:javascript
复制
Input[28]:
# Normalize skewed features
for i in skew_index:
    all_features[i] = boxcox1p(all_features[i], 
    boxcox_normmax(all_features[i] + 1))

Input[29]:

代码语言:javascript
复制

从上图可以看到,所有特征都看上去呈正态分布了。

创建一些有用的特征

机器学习模型对复杂模型的认知较差,因此我们需要用我们的直觉来构建有效的特征,从而帮助模型更加有效的学习。

代码语言:javascript
复制

特征转换

通过对特征取对数或者平方,可以创造更多的特征,这些操作有利于发掘潜在的有用特征。

代码语言:javascript
复制
代码语言:javascript
复制

对集合特征进行编码

对集合特征进行数值编码,使得机器学习模型能够处理这些特征。

代码语言:javascript
复制
all_features = pd.get_dummies(all_features).reset_index(drop=True)
all_features.shape

(2917, 379)

代码语言:javascript
复制
代码语言:javascript
复制
all_features.head()
代码语言:javascript
复制
代码语言:javascript
复制
代码语言:javascript
复制
all_features.shape
代码语言:javascript
复制
(2917, 379)
代码语言:javascript
复制

重新创建训练集和测试集

代码语言:javascript
复制
X = all_features.iloc[:len(train_labels), :]
X_test = all_features.iloc[len(train_labels):, :]
X.shape, train_labels.shape, X_test.shape

((1458, 378), (1458,), (1459, 378))

对训练集中的部分特征进行可视化

代码语言:javascript
复制
# Finding numeric features
numeric_dtypes = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
numeric = []
for i in X.columns:
    if X[i].dtype in numeric_dtypes:
        if i in ['TotalSF', 'Total_Bathrooms','Total_porch_sf','haspool','hasgarage','hasbsmt','hasfireplace']:
            pass
        else:
            numeric.append(i)
# visualising some more outliers in the data values
fig, axs = plt.subplots(ncols=2, nrows=0, figsize=(12, 150))
plt.subplots_adjust(right=2)
plt.subplots_adjust(top=2)
sns.color_palette("husl", 8)
for i, feature in enumerate(list(X[numeric]), 1):
    if(feature=='MiscVal'):
        break
    plt.subplot(len(list(numeric)), 3, i)
    sns.scatterplot(x=feature, y='SalePrice', hue='SalePrice', palette='Blues', data=train)
        
    plt.xlabel('{}'.format(feature), size=15,labelpad=12.5)
    plt.ylabel('SalePrice', size=15, labelpad=12.5)
    
    for j in range(2):
        plt.tick_params(axis='x', labelsize=12)
        plt.tick_params(axis='y', labelsize=12)
    
    plt.legend(loc='best', prop={'size': 10})
        
plt.show()

模型训练

模型训练过程中的重要细节

  • 交叉验证:使用12-折交叉验证
  • 模型:在每次交叉验证中,同时训练七个模型(ridge, svr, gradient boosting, random forest, xgboost, lightgbm regressors)
  • Stacking 方法:使用xgboot训练了元 StackingCVRegressor 学习器
  • 模型融合:所有训练的模型都会在不同程度上过拟合,因此,为了做出最终的预测,将这些模型进行了融合,得到了鲁棒性更强的预测结果

初始化交叉验证,定义误差评估指标

代码语言:javascript
复制
代码语言:javascript
复制
# Setup cross validation folds
kf = KFold(n_splits=12, random_state=42, shuffle=True)
代码语言:javascript
复制

建立模型

代码语言:javascript
复制

训练模型

计算每个模型的交叉验证的得分

代码语言:javascript
复制
scores = {}

score = cv_rmse(lightgbm)
print("lightgbm: {:.4f} ({:.4f})".format(score.mean(), score.std()))
scores['lgb'] = (score.mean(), score.std())

lightgbm: 0.1159 (0.0167)

代码语言:javascript
复制
score = cv_rmse(xgboost)
print("xgboost: {:.4f} ({:.4f})".format(score.mean(), score.std()))
scores['xgb'] = (score.mean(), score.std())

xgboost: 0.1364 (0.0175)

代码语言:javascript
复制
score = cv_rmse(svr)
print("SVR: {:.4f} ({:.4f})".format(score.mean(), score.std()))
scores['svr'] = (score.mean(), score.std())

SVR: 0.1094 (0.0200)

代码语言:javascript
复制
score = cv_rmse(ridge)
print("ridge: {:.4f} ({:.4f})".format(score.mean(), score.std()))
scores['ridge'] = (score.mean(), score.std())

ridge: 0.1101 (0.0161)

代码语言:javascript
复制
score = cv_rmse(rf)
print("rf: {:.4f} ({:.4f})".format(score.mean(), score.std()))
scores['rf'] = (score.mean(), score.std())

rf: 0.1366 (0.0188

代码语言:javascript
复制
代码语言:javascript
复制
score = cv_rmse(gbr)
print("gbr: {:.4f} ({:.4f})".format(score.mean(), score.std()))
scores['gbr'] = (score.mean(), score.std())
代码语言:javascript
复制
gbr: 0.1121 (0.0164)

拟合模型

代码语言:javascript
复制
代码语言:javascript
复制
print('stack_gen')
stack_gen_model = stack_gen.fit(np.array(X), np.array(train_labels))
代码语言:javascript
复制
stack_gen
代码语言:javascript
复制
print('lightgbm')
lgb_model_full_data = lightgbm.fit(X, train_labels)

lightgbm

代码语言:javascript
复制
print('xgboost')
xgb_model_full_data = xgboost.fit(X, train_labels)

xgboost

代码语言:javascript
复制
print('Svr')
svr_model_full_data = svr.fit(X, train_labels)

Svr

代码语言:javascript
复制
print('Ridge')
ridge_model_full_data = ridge.fit(X, train_labels)

Ridge

代码语言:javascript
复制
print('RandomForest')
rf_model_full_data = rf.fit(X, train_labels)

RandomForest

代码语言:javascript
复制
print('GradientBoosting')
gbr_model_full_data = gbr.fit(X, train_labels)

GradientBoosting

融合各个模型,并进行最终预测

代码语言:javascript
复制
# Blend models in order to make the final predictions more robust to overfitting
def blended_predictions(X):
    return ((0.1 * ridge_model_full_data.predict(X)) + \
            (0.2 * svr_model_full_data.predict(X)) + \
            (0.1 * gbr_model_full_data.predict(X)) + \
            (0.1 * xgb_model_full_data.predict(X)) + \
            (0.1 * lgb_model_full_data.predict(X)) + \
            (0.05 * rf_model_full_data.predict(X)) + \
            (0.35 * stack_gen_model.predict(np.array(X))))
代码语言:javascript
复制
# Get final precitions from the blended model
blended_score = rmsle(train_labels, blended_predictions(X))
scores['blended'] = (blended_score, 0)
print('RMSLE score on train data:')
print(blended_score)

RMSLE score on train data: 0.07537440195302639

各模型性能比较

代码语言:javascript
复制

从上图可以看出,融合后的模型性能最好,RMSE 仅为 0.075,该融合模型用于最终预测。

提交预测结果

代码语言:javascript
复制

(1459, 2)

代码语言:javascript
复制
代码语言:javascript
复制
# Append predictions from blended models
submission.iloc[:,1] = np.floor(np.expm1(blended_predictions(X_test)))
 
# Fix outleir predictions
q1 = submission['SalePrice'].quantile(0.0045)
q2 = submission['SalePrice'].quantile(0.99)
submission['SalePrice'] = submission['SalePrice'].apply(lambda x: x if x > q1 else x*0.77)
submission['SalePrice'] = submission['SalePrice'].apply(lambda x: x if x < q2 else x*1.1)
submission.to_csv("submission_regression1.csv", index=False)
代码语言:javascript
复制
代码语言:javascript
复制

(*本文为 AI科技大本营翻译文章,转载请联系 1092722531

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 AI科技大本营 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Top 0.3% 模型概览
    • 赛题和目标
      • 模型训练过程中的重要细节
        • 模型性能
        • EDA
          • 对原始数据进行可视化
            • SalePrice:目标值的特性探究
            • ((1460, 80), (1459, 79)) 可视化 salePrice 的分布
              • 填充缺失值
                • 调整分布倾斜的特征
                  • 创建一些有用的特征
                    • 特征转换
                      • 对集合特征进行编码
                        • 重新创建训练集和测试集
                        • 模型训练
                          • 模型训练过程中的重要细节
                            • 初始化交叉验证,定义误差评估指标
                              • 建立模型
                                • 训练模型
                                  • 融合各个模型,并进行最终预测
                                    • 各模型性能比较
                                      • 提交预测结果
                                      领券
                                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档