一次完整的数据挖掘过程

任务描述

kaggle 案例 california-housing-prices
https://www.kaggle.com/camnugent/california-housing-prices

基于给定的数据,训练模型预测某一区域的房价中位数

房价数据包括人口 . 收入中位数 . 房价中位数 等对于每个街区的描述属性

设计问题解决方案时应该了解到的信息:

  1. 弄清楚模型的应用目的.
  2. 大致弄明白当前(非机器学习模型的condition下)是如何的解答该应用问题.

接下来,你需要设计采用哪种模型解决问题.监督学习?无监督学习?增强学习?从问题类型上来分,是一个分类问题?还是一个回归问题?亦或者是其他的问题?需要采用分批次学习还是在线学习?

挑选一个性能评价指标:


  • 均方根误差RMSE
    RMSE.png

RMSE是一种常用的测量数值之间差异的度量,其代表预测的值和观察到的值之差的样本标准差.
比如 :根据正态分布的结论,RMSE=50,000,这意味着模型预测的68%的结果落在预测值左右(+ -)50,000的范围之内.有95%的预测落在2倍标准差即100,000的范围内.因此较好地描述了预测的性能.

  • 均值绝对误差MAE
    MAE.png
    RMSE和MAE都是一种衡量两个向量之间差值距离的方式,总的来说RMSE更优也更常用.但是在样本中存在很多离群点的时候,你就应该考虑使用MAE对其进行衡量,因为MAE相对RMSE而言对离群值不那么敏感.
    • RMSE对应于欧几里得范式,也称之为二范式.
    • MAE对应于一范式.有时又称为曼哈顿范式.
    • 更一般的情况,也存在k范式.

快速了解数据:
import pandas as pd
housing = pd.read_csv('housing.csv')
housing.head()          # 查看DF的前5行

housing.head().png

可以用info()方法简要查看数据的描述.特别是数据的行数,每个特征的数据类型,以及非空值的数量. 可以了解到:

  • 20000左右的数据量,对于机器学习模型来说算是一个小的数据集
  • 可以看到total_bedrooms这一属性只有20433个非空值,即有207个缺失值,这是值得格外注意的.
In: housing.info()
Out:
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 20640 entries, 0 to 20639
    Data columns (total 10 columns):
    longitude             20640 non-null float64
    latitude              20640 non-null float64
    housing_median_age    20640 non-null float64
    total_rooms           20640 non-null float64
    total_bedrooms        20433 non-null float64
    population            20640 non-null float64
    households            20640 non-null float64
    median_income         20640 non-null float64
    median_house_value    20640 non-null float64
    ocean_proximity       20640 non-null object
    dtypes: float64(9), object(1)
    memory usage: 1.6+ MB

通过对于前5行的浏览,发现ocean_proximity这一属性为object类型,并且值是重复的,这也意味着这可能是一个类别属性.因此,我们可以用value_counts( )方法弄清白到底有哪些类,并且每个类中到底存在多少实例.

In: type(housing['ocean_proximity'])

Out: pandas.core.series.Series

In: housing['ocean_proximity'].value_counts()
  
Out:  
<1H OCEAN     9136
INLAND        6551
NEAR OCEAN    2658
NEAR BAY      2290
ISLAND           5
Name: ocean_proximity, dtype: int64

housing.describe()`方法可以展示数值特征的概要

In: housing.describe()

housing.describe().png

另外一个快速整体认知数据的方法是绘制每一个属性的直方图.

  • hist()方法是依赖于matplotlib包的,因此在使用之前必须先申明基于matplotlib.这个操作可以使用Jupyter的魔法命令%matplotlib inline.这能够告知Jupyter启用matplotlib.
%matplotlib inline
import matplotlib.pyplot as plt
housing.hist(bins=50, figsize=(20,15))

output_16_1.png

从直方图中得到的总结:

  1. 特征median_income数值的单位并不是dollar.已经被缩放并capped(设置阈值,这里是[0.5, 15])处理过了.在训练的时候这无关紧要,但是需要知道这些数据大概是如何计算得到.
  2. housing_median_age和median_house_value也是明显被capped过(注意到在上界出现了峰值).这将是一个需要异常注意的问题,因为median_house_value将是我们模型应用的标签,即ML模型训练后得出的任何预测几乎都不可能超过这个上界.因此,在模型应用的时候需要格外地注意,目标应用和训练集上的数值差别
  3. 这些属性存在多个不同的规模量级,这也就需要在后面进行特征归一化.
  4. 很多直方图显示出的特征数据分布都是尾部大的分布,这对于机器学习模型来说是不太好训练的.因此我们需要将这些特征的分布修正为正态分布.

创建测试集

数据窥探误差:目前为止我们只是对数据快速瞥了一眼,在挑选真正的模型之前我们还有很多信息需要学习.如果在这之前我们只是对test_set进行一个窥探,那么很容易造成认识上的错觉,倾向于选择某一个特定的模型进行训练.然鹅,大部分情况下这种都是过于乐观的操作.这就是叫做数据窥探误差

 import numpy as np

自己写的一个拆分方法

def split_train_test(data,test_radio):
    shuffled_indices = np.random.permutation(len(data))             # 产生一个目标长度的乱序索引
    test_set_size = int(len(data) * test_radio)                      #指定测试集的数量
    test_indices = shuffled_indices[:test_set_size]
    train_indices = shuffled_indices[test_set_size:]
    return data.iloc[train_indices],data.iloc[test_indices]

易混淆的iloc和loc的区分::
在pandas中loc和iloc都能实现对目标数据的准确索引.但实质上是有区别的:

  • loc['a','b' ]中填入的是行列位置标签
  • iloc[a,b] 中填入的索引
 train_set,test_set = split_train_test(housing,0.2)

上述方法当然有效,但是并不完美.因为如果你重新run这一程序,你将拿到不同的测试集.慢慢的,你的算法将可能看到整个数据集,而这是你应该避免的.

# 查看拆分的效果
In: train_set.shape
Out: (16512, 10)

In: test_set.shape
Out: (4128, 10)

sklearn的拆分方法

from sklearn.model_selection import train_test_split
train_set,test_set = train_test_split(housing, test_size=0.2, random_state=42)

分层采样

  1. 直接随机采样有什么弊端?
     当你的数据集足够大时,一般来说随机采样都是可行的.但是如果数据量不够大,那么随机采样则可能有样本严重偏斜的风险.

  2. 为什么要进行分层采样?
     分层抽样比单纯随机抽样所得到的结果准确性更高,组织管理更方便,而且它能保证总体中每一层都有个体被抽到,从而样本集对于总体来说会更加有代表性。这样除了能估计总体的参数值,还可以分别估计各个层内的情况,因此分层抽样技术常被采用。

  3. 实例介绍
     例如,通过对包含1000个样本的数据集D进行分层抽样而获得70%样本的训练集S和含30%样本的测试集T,若D包含500个正例、500个反例,则分层采样得到的S应包含350个正例、350个反例,而T则包含150个正例、150个反例;
    若S、T中样本类别比例差别很大,则误差估计将由于训练/测试数据分布的差异而产生偏差。

In: housing['median_income'].hist()
Out: <matplotlib.axes._subplots.AxesSubplot at 0x2070de81dd8>

housing['median_income'].hist().png

假设median_income属性非常重要:

 通过直方图可以看出,大多数的income集中在2-5 unit之间.但是也是有一些income和2-5的范围差得很远,比如tail部分.
在这种情况下,保证数据集中income衡量的特征中每一层都有足够数量的实例是很有必要的,否则就会产生一些偏差.同时也这意味着不能有太多的分层,并且每个分层是需要足够大的.
 后面的代码通过将收入中位数除以 1.5(以限制收入分类的层次数量),创建了一个收入类别属性,并且需要用ceil对值舍入(尽量产生离散的分类),然后将所有大于 5的分类归入到分类5.

housing['income_cat'] = np.ceil(housing['median_income'] / 1.5)            # 添加income_cat属性,辅助分层
housing['income_cat'].where(housing['income_cat']<5, 5.0, inplace=True)    # where方法操作

接下来准备基于income类对数据进行分层采样.这个问题需要用到sklearn中的StratifiedShuffleSplit class.

from sklearn.model_selection import StratifiedShuffleSplit
# random_state 依然是随机种子生成器
# n_split 是将训练数据分成train-test对的对数.-->我们这个地方汇总为一组数据
split = StratifiedShuffleSplit(n_splits=1,test_size=0.2,random_state=42)

for train_index,test_index in split.split(housing, housing['income_cat']):
    strat_train_set = housing.loc[train_index]
    strat_test_set  = housing.loc[test_index]

查看在整个housing数据集中income 属性类别的比例,瞥一眼看看是否成功分层采样.

In: housing['income_cat'].value_counts() / len(housing)
Out: 
    3.0    0.350581
    2.0    0.318847
    4.0    0.176308
    5.0    0.114438
    1.0    0.039826
    Name: income_cat, dtype: float64

完成分割后,接下来务必将数据集的特征还原,即丢掉income_cat属性:

# 完成分割,删除income_cat属性
for set in (strat_train_set,strat_test_set):
    set.drop(['income_cat'], axis=1,inplace=True)

客观上来讲,对于本例20,000个样本的小集合来说,上述得到的训练集会有利于模型训练.

可视化数据寻找规律

接下来需要更加地了解数据.
复制一份数据,避免训练集被损坏.

housing = strat_train_set.copy()

看一下人口密度的特征:

In: import matplotlib.pyplot as plt
    housing.plot(kind='scatter',x='longitude',y='latitude')
Out: <matplotlib.axes._subplots.AxesSubplot at 0x2070b54c978>

image.png

可以看到California的轮廓.但是这个整个很难看出特点.因此设置alpha = 0.1,使得视图可以区分分布点的密度.

In: housing.plot(kind='scatter',x='longitude',y='latitude',alpha = 0.1)
Out: <matplotlib.axes._subplots.AxesSubplot at 0x2070e36b390>

image.png

基于上述图就可以清楚地看到高人口密度的区域了.大多集中在海湾和城市.

接下来看房价特征:

In: housing.plot(kind='scatter',x='longitude',y='latitude',alpha=0.4,
            s=housing['population']/100,label='population',c='median_house_value',cmap=
                     plt.get_cmap('jet'),colorbar=True,figsize=(12,7),legend=True,use_index=True)

Out: <matplotlib.axes._subplots.AxesSubplot at 0x2070e3d7358>

房价特征图.png

每个圈的半径表示街区的人口(选项s),颜色代表价格(选项c)。我们用预先定义的名为jet的颜色图(选项cmap),它的范围是从蓝色(低价)到红色(高价):
这张图说明房价和位置,还有人口密度联系密切. 这对于使用聚类算法去分析主要的簇可能是有帮助的,并且能够添加衡量是否邻近'簇'中心的新特征.

搜索关联性

数据集较小,可以轻易使用corr()方法计算出每对属性间的标准相关系数(也称作皮尔逊相关系数)

In: corr_matrix = housing.corr()
In: corr_matrix.shape,type(corr_matrix)
Out: ((9, 9), pandas.core.frame.DataFrame)
In: corr_matrix

corr_matrix.png

相关系数的范围为-1到1. 越接近1,意味着强正相关;例如收入中位数越大,大概率上房价中位数也会很大.当相关系数越接近-1,意味着负相关越强.相关系数越接近0,越是意味着两种feature之间没有直接的线性关系.

相关系数关系表述图.png

上图表述了不同的分布图所描述的两种特征的相关程度.
需要注意的是:关联系数仅仅衡量了线性的关联,它可能完全无法刻画出非线性的关系.比如第3行,其实能看出特征之间还是有明显的关系的,只不过不是线性关系罢了.

In: corr_matrix['median_house_value'].sort_values(ascending=False)
Out:
    median_house_value    1.000000
    median_income         0.687160
    total_rooms           0.135097
    housing_median_age    0.114110
    households            0.064506
    total_bedrooms        0.047689
    population           -0.026920
    longitude            -0.047432
    latitude             -0.142724
    Name: median_house_value, dtype: float64

可以看到median_house_value 与median_income 的相关性是非常强的.

另一种检测属性间相关系数的方法是使用 Pandas 的scatter_matrix函数,它能画出每个数值属性对每个其它数值属性的图。因为现在共有 11 个数值属性,你可以得到11 ** 2 = 121张图。
scatter_matrix.png

from pandas.tools.plotting import scatter_matrix
attributes = ["median_house_value", "median_income", "total_rooms",
              "housing_median_age"]
scatter_matrix(housing[attributes],figsize=(10,10))

seaborn中的pairplot有更好的相关系数图绘图效果

import seaborn as sns
attributes = ["median_house_value", "median_income", "total_rooms",
              "housing_median_age"]
f1 = sns.pairplot(housing[attributes],diag_kind="kde")

image.png

其实对于预测median_house_value最优希望的特征就是median_income.

# 绘图查看median_house_value和median_income的相关程度
In: housing.plot(kind='scatter',x='median_income',y='median_house_value',alpha=0.1) 
Out: <matplotlib.axes._subplots.AxesSubplot at 0x20710c25160>

image.png

图中揭示了:

  1. median_income和median_house_value的关联确实非常强,可以看到明显的集中和上升变化趋势.
  2. 在图中可以看到不止一条value分层线,50000是预先设定,同时还能看到45000 . 35000等等. 这些对应的区域其实可能是需要被去掉的,否则可能会造成错误的分布特点.

尝试特征组合

在为算法准备数据之前,还需要做的一个步骤就是尝试组合出各种各样的特征.将一些没有多大用处的特征利用起来,通过运算组合得到一些新的有意义的特征,这一步也是比较重要的,能够有效提升模型效果.
比如:

  • 在本例中total_rooms这一属性直接利用是没有多少价值的,知道一个区域的总房间数似乎无用.
  • 我们真正要用的上是每个家庭有多少间屋子.
  • 同理,total_bedrooms也是没有意义的,可以构造出bedrooms_per_room属性.
  • 同时population_per_household似乎也是一个有意思的属性.
housing['rooms_per_household'] = housing['total_rooms']/housing['households']
housing['bedrooms_per_room'] = housing['total_bedrooms']/housing['total_rooms']
housing['population_per_household'] = housing['population']/housing['households']
housing.head()

组合特征.png

完成组合,查看一下皮尔逊相关系数:

In: corr_matrix = housing.corr()
Out: 
    corr_matrix['median_house_value'].sort_values(ascending=False)
    median_house_value          1.000000
    median_income               0.687160
    rooms_per_household         0.146285
    total_rooms                 0.135097
    housing_median_age          0.114110
    households                  0.064506
    total_bedrooms              0.047689
    population_per_household   -0.021985
    population                 -0.026920
    longitude                  -0.047432
    latitude                   -0.142724
    bedrooms_per_room          -0.259984
    Name: median_house_value, dtype: float64

可以发现我们构造的bedrooms_per_room属性,还有rooms_per_household属性与目标的相关性是明显高于total_rooms和households/total_rooms的.说明构造还是比较成功的.

添加特征是一个不断迭代循环的过程,一旦模型得到提升,就可以再次分析其输出,拿到更多信息**并且返回这一探索的步骤.

为机器学习算法准备数据

数据清洗

数据清洗之前,需要再将strat_train_set复制一份.
并且需要将数据源和标签分开,因为两者进行的是不一样的操作.

housing = strat_train_set.drop('median_house_value',axis=1)
housing_labels = strat_train_set['median_house_value'].copy()

drop()方法创建了一个data的副本,同时还并不会影响strat_train_set

大多机器学习算法不能处理特征丢失,因此先创建一些函数来处理特征丢失的问题。前面,注意到属性total_bedrooms有一些缺失值。
针对这个问题有三个解决选项:

  • 去掉对应的街区;
  • 去掉整个属性;
  • 进行赋值(0、平均值、中位数等等)

用DataFrame的dropna(),drop(),和fillna()方法,可以方便地实现:

housing.dropna(subset=['total_bedrooms'])        #dropna丢弃该行数据,指定到  total_bedrooms列去寻找缺失数据,axis默认为0-->行丢弃
housing.drop('total_bedrooms',axis=1)            # 去掉整个属性  
median = housing['total_bedrooms'].median()    
housing['total_bedrooms'].fillna(median)         # 用中位数进行填充
scikit-learn 也提供了一个方便的类来处理缺失值:Imputer.用法如下:
  • 首先需要创建一个Imputer实例,指定用中位数替换它的每个缺失值
In: from sklearn.preprocessing import Imputer
In: imputer = Imputer(strategy='median')
In: imputer      # imputer实例
Out: Imputer(axis=0, copy=True, missing_values='NaN', strategy='median', verbose=0)

只有数值属性才能计算出中位数,因此也需要创建一份不包括文本属性ocean_proximity的数据副本:

housing_num = housing.drop('ocean_proximity',axis=1).copy()    # 只有这样的样本才不会计算报错

现在,就可以用fit()方法将imputer实例拟合到训练数据:

In: imputer.fit(housing_num)
Out: Imputer(axis=0, copy=True, missing_values='NaN', strategy='median', verbose=0)

imputer实例在这个过程中简单计算了每一个属性的median value,并且存在了statistics_属性当中.

In: imputer.statistics_
Out: array([-118.51  ,   34.26  ,   29.    , 2119.5   ,  433.    , 1164.    ,408.    ,    3.5409])

In: housing_num.median().values
Out: array([-118.51  ,   34.26  ,   29.    , 2119.5   ,  433.    , 1164.    , 408.    ,    3.5409])

使用训练过的imputer对训练集进行转换变形

In: X = imputer.transform(housing_num)
In: X.shape,type(X)
Out: ((16512, 8), numpy.ndarray)

housing_num转换后变成了ndarray的格式,当然也可以放回DF中

housing_tr = pd.DataFrame(X,columns=housing_num.columns)
housing_tr.head()

housing_tr.head().png

处理文本和类别属性

在前面,为了处理缺失的数据值,我们去掉了属性ocean_proximity,因为文本属性并没有中位数.
为了让机器学习算法能利用文本标签,我们需要把文本转换为数字.
scikit-learn提供了LabelEncoder:

In: from sklearn.preprocessing import LabelEncoder
In: encoder = LabelEncoder()
In: housing_cat = housing['ocean_proximity']
In: housing_cat_encoded= encoder.fit_transform(housing_cat)
In: housing_cat_encoded
Out: array([0, 0, 4, ..., 1, 0, 3])

转换后的数字量就可以用于任何ML模型的学习.同时还可以通过encoder.classes_查看数值和类别的mapping映射关系

In: encoder.classes_    # 查看分出的类别
Out: array(['<1H OCEAN', 'INLAND', 'ISLAND', 'NEAR BAY', 'NEAR OCEAN'],dtype=object)

LabelEncoder还不够好 ! 为什么?
对于LabelEncoder来说,其生成的标签数值并非是二值,本例中出现了0,1,2,3,4.将这样的数据喂给ML模型,模型会误认为类别的相似度由数值的大小影响.为了解决这样的问题,引入了one-hot解决方案.

One-Hot:
要更优地处理离散特征这还不够,Scikit-Learn 提供了一个编码器OneHotEncoder,用于将整数分类值转变为独热向量。结果是为每一个类别都创建了一个二值属性,值为'1'时,代表属于该类(hot),否则均为'0'(cold).
利用encoder.fit_transform()之前需要注意该方法用于处理2d数组,因此需要先将housing_cat_encoded变形.

In: from sklearn.preprocessing import OneHotEncoder
In: encoder = OneHotEncoder()
In: housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.reshape(-1,1))    # 变形操作
In: housing_cat_1hot
Out: <16512x5 sparse matrix of type '<class 'numpy.float64'>'with 16512 stored elements in Compressed Sparse Row format>

注意到上述的housing_cat_1hot结果是一个SciPy系数矩阵,而非一个Nunmpy array.在有成千上万的类别属性的时候这是非常有用的,可以节省很多空间,因为不用存储0.

以上的步骤也都可以用LabelBinarizer一步搞定,即从文本到整数分类,再转换成one-hot类

In: from sklearn.preprocessing import LabelBinarizer
In: encoder = LabelBinarizer(sparse_output = False)
In: housing_cat_1hot = encoder.fit_transform(housing_cat)

In: housing_cat_1hot
Out:
    array([[1, 0, 0, 0, 0],
           [1, 0, 0, 0, 0],
           [0, 0, 0, 0, 1],
           ...,
           [0, 1, 0, 0, 0],
           [1, 0, 0, 0, 0],
           [0, 0, 0, 1, 0]])

自定义转换器

尽管scikit-learn提供了很多有用的转换器,你依然需要手动编写,以满足自定义数据清洗操作和特征组合的需要.你想要你的转换器和scikit-learn库运行时可以无缝连接(比如管道),因为scikit-learn依赖于鸭子类型(而非继承),你需要做的所有就是创造一个类并执行fit()(返回self) . transform() 和 fit_transform()这三个方法. 如果你只添加TransformerMixin作为基类,你就可以不用最后一个方法.如果你添加BaseEstimator作为基类(且构造器中避免使用args和kargs),你就能得到两个额外的方法(get_params()和set_params()),二者可以方便地进行超参数自动微调。

例如,下面这个小转换器类添加了上面讨论的属性:

from sklearn.base import BaseEstimator, TransformerMixin
rooms_ix, bedrooms_ix, population_ix, household_ix = 3, 4, 5, 6

# 这里的示例没有定义fit_transform(), 因为可以直接用fit 和 transform步骤达到同样的效果
class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
    def __init__(self, add_bedrooms_per_room = True):           # no *args or **kargs
        self.add_bedrooms_per_room = add_bedrooms_per_room
    def fit(self, X, y=None):
        return self  # nothing else to do
    def transform(self, X, y=None):        # 创造新的特征
        rooms_per_household = X[:, rooms_ix] / X[:, household_ix]            # X[:,3]表示的是第4列所有数据
        population_per_household = X[:, population_ix] / X[:, household_ix]
        if self.add_bedrooms_per_room:
            bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
            return np.c_[X, rooms_per_household, population_per_household,   # np.c_表示的是拼接数组。
                         bedrooms_per_room]
        else:
            return np.c_[X, rooms_per_household, population_per_household]
In: attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)
In: housing_extra_attribs = attr_adder.transform(housing.values)      # 返回一个加入新特征的数据集合
In: housing.shape
Out: (16512, 9)
In: housing_extra_attribs.shape
Out: (16512, 11)

在这个例子中,转换器只有一个超参数,即add_bedrooms_per_room这个布尔量,默认设置为True(提供一个明智的默认设置通常是很有帮助的).这个超参数会让你轻易地发现添加这个特征是否对ML算法的有好处.更一般的,你可以往gate中添加一个你在数据准备阶段不能完全确定其作用的超参数.数据准备步骤越是自动化,可以自动化解析出的特征组合就越多,这也使得你更可能找到一个非常好的特征组合(这将节省你大量时间)


这里需要区分一下fit() . transform(). 和fit_transform():

  • fit()是很好理解的,就是训练拟合,学习到相关参数.
  • transform()和fit_transform()看起来是非常相似,也是很容易混淆的,实质上两种有重大区别:
    • fit_transform()用在训练集当中.是fit()和transform()的组合,可以直接完成两个步骤.
    • transform()如上所述,只有转化的功能,用于测试集当中.但是这时不需要fit的原因是转换相关的参数都已经拿到了.
  • fit()是后续所有api的先决条件
  • fit_transform和transform的效果其实是没有区别的(正确使用的前提下).

此外,scikit-learn无法直接处理DataFrame,因此需要自定义一个方法能够实现向numpy的转换

class DataFrameSelector(BaseEstimator,TransformerMixin):
    def __init__(self,attribute_names):
        self.attribute_names = attribute_names
    def fit(self,X,y=None):
        return self
    def transform(self,X):
        return X[self.attribute_names].values        # .values实现取出所有的值, 为数组的形式

数据归一化

你需要对你数据进行的最重要的转化之一就是特征归一化.在输入特征有不同的量级的时候ML模型的表现通常不好.对于本数据集来说,同样存在这一的情况.
需要注意的是目标值value通常是不需要进行归一化的

有两种常见的方法可以让所有的属性有相同的量度:线性函数归一化(Min-Max scaling)和标准化(standardization)。

Min-Max scaling:
Min-Max scaling.png

Min-Max scaling是一种非常简单的概念,可以将所有的值重新缩放至0,1的范围内.Scikit-Learn 提供了一个转换器MinMaxScaler来实现这个功能。它有一个超参数feature_range,可以让你改变范围,如果不希望范围是 0 到 1;Scikit-Learn 提供了一个转换器StandardScaler来进行标准化

Standardization:
Standardization就是另外一种完全不同的方法.
Standardization.png

  • 标准化不会将数值约束到一个特定的范围内,这对于一些特定的算法(比如神经网络)是不太合适的
  • 标准化受到离群值的影响较小.相对而言,Min-Max scaling会受到更大的影响.
  • 标准化后的结果分布不会有单位偏差

和所有的变换一样,scaler只能用训练数据进行训练,而不能整个数据集拟合,这是需要格外注意的地方.只有这样我们才能用它来对训练集和测试集进行变形.

from sklearn.preprocessing import MinMaxScaler,StandardScaler

转换管道

有很多数据转换的步骤需要以正确的顺序被执行.scikit-learn中就提供了Pipeline类去解决顺序转换的问题.

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

num_pipeline = Pipeline([
    ('imputer',Imputer(strategy='median')),          # 缺失值填充
    ('attribs_adder',CombinedAttributesAdder()),    # 合成新属性
    ('std_scaler',StandardScaler())                # 数据归一化
])

In: housing_num_tr = num_pipeline.fit_transform(housing_num)

Pipeline构造器需要传入定义一系列步骤的名字/预测器对的列表.除开最后一个预测器,所有的预测器都需要是一个转换器(必须要有fit_transform()方法).管道的名字可以随意取.

当对pipeline使用fit()方法时,他会自动顺序调用所有转换器的fit_transform()方法,将每一个调用方法的输出结果作为参数传给下一个方法调用,直到run到了最后一个预测器,这个预测器只有fit()方法.

pipeline公开了和最终预测器相同的方法.在本例中最后一个预测器是StandardScaler,这是一个转换器,因此pipeline有一个顺序地将所有转换器运用到data上的transform()方法.(它还有一个fit_transform方法,这个方法可以代替fit()和transform()的顺序执行)

注意,pipeline最后一步如果有predict()方法我们才可以对pipeline使用fit_predict(),同理,最后一步如果有transform()方法我们才可以对pipeline使用fit_transform()方法。

为了将处理类别值的LabelBinary方法和先前处理numerical的pipeline进行组合,将这些变换组合到单个的管道中:Scikit-Learn提供了FeatureUnion类解决这个问题.

  • 你可以传入一个转换器的列表(还可以是一整个转换器管道)
  • 运行的时候管道会将每一个转换器的输出融合并返回.
from sklearn.pipeline import FeatureUnion

实际编码中,笔者遇到pipeline传参个数出错的问题,查阅资料后得出以下解决方案:

  1. The pipeline is assuming LabelBinarizer's fit_transform method is defined to take three positional arguments,This can be solved by making a custom transformer that can handle 3 positional arguments.
  2. Since LabelBinarizer doesn't allow more than 2 positional arguments you should create your custom binarizer like,solution2 is following
solution_1:
from sklearn.base import TransformerMixin           # gives fit_transform method for free
class MyLabelBinarizer(TransformerMixin):
    def __init__(self, *args, **kwargs):
        self.encoder = LabelBinarizer(*args, **kwargs)
    def fit(self, x, y=0):
        self.encoder.fit(x)
        return self
    def transform(self, x, y=0):
        return self.encoder.transform(x)

solution2:
class CustomLabelBinarizer(BaseEstimator, TransformerMixin):
    def __init__(self, sparse_output=False):
        self.sparse_output = sparse_output
    def fit(self, X, y=None):
        return self
    def transform(self, X, y=None):
        enc = LabelBinarizer(sparse_output=self.sparse_output)
        return enc.fit_transform(X)
管道组合

In: num_attribs = list(housing_num)
In: cat_attribs = ['ocean_proximity']

In: num_pipeline = Pipeline([
    ('selector',DataFrameSelector(num_attribs)),
    ('imputer',Imputer(strategy = 'median')),
    ('attribs_adder',CombinedAttributesAdder()),
    ('std_scaler',StandardScaler()),
])

In: cat_pipeline = Pipeline([
    ('selector',DataFrameSelector(cat_attribs)),
    ('label_binarizer',CustomLabelBinarizer()), 
])

In: full_pipeline = FeatureUnion(transformer_list=[
    ('num_pipeline',num_pipeline),
    ('cat_pipeline',cat_pipeline),
])
In: housing.shape
Out: (16512, 9)
In: housing_prepared = full_pipeline.fit_transform(housing)
In: housing_prepared.shape
Out: (16512, 16)      # 已经在管道中完成转换
In: type(housing_prepared)  #housing_prepared是一个ndarray的形式
Out: numpy.ndarray

每一个子管道都从选择转换器开始:完成的任务是挑选需要的目标属性,抛弃其他的属性,将DataFrame转换成Numpy array.在scikit-learn中没有任何处理DataFrame的函数,因此我们需要自己写一个简单的转换器.

from sklearn.base import BaseEstimator,TransformerMixin
class DataFrameSelector(BaseEstimator,TransformerMixin):
    def __init__(self,attribute_names):
        self.attribute_names = attribute_names
    def fit(self,X,y=None):
        return self
    def transform(self,X):
        return X[self.attribute_names].values    # 保证返回的是一个array

注释以下概念:
每一个scikit-learn中的扩展都需要继承特定的sklearn.base下的包的类:

  • BaseEstimator 估计器的基类
  • ClassifierMixin 分类器的混合类
  • ClusterMixin 聚类器的混合类
  • RegressorMixin 回归器的混合类
  • TransformerMixin 转换器的混合类

创建了DataFrameSelector类继承了BaseEstimator,TransformerMixin意味着它的基本功能就是单一估计器和转换器的组合

模型选择与训练

设计问题 , 拿到数据+瞥一眼 , 取出测试集与训练集 , 运用转换管道清洗数据 . 接下来就该挑选并训练ML模型

首先训练一个线性回归的模型.

In: from sklearn.linear_model import LinearRegression
In: lin_reg = LinearRegression()
In: lin_reg.fit(housing_prepared, housing_labels)
Out: LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None,normalize=False)
In: housing_prepared.shape
Out: (16512, 16)

现在已经拿到一个有效的线性回归模型了.
为了瞄一眼效果,可以从训练集中取一些数据拿来尝试

In: some_data = housing_prepared[:5]
In: some_data.shape
Out: (5, 16)
In: some_labels = housing_labels.iloc[:5]
In: 'Predictions:\t',lin_reg.predict(some_data)
Out: ('Predictions:\t', array([210644.60459286, 317768.80697211, 210956.43331178,  59218.98886849,189747.55849879]))
In: 'Predictions:\t',list(some_labels)
Out: ('Predictions:\t', [286600.0, 340600.0, 196900.0, 46300.0, 254500.0])

iloc和loc的区分::
在pandas中loc和iloc都能实现对目标数据的准确索引.但实质上是有区别的:

  • loc['a','b' ]中填入的是行列位置标签
  • iloc[a,b] 中填入的索引

我们拿到了我们的预测值,尽管偏差还是蛮大的.
我们可以用RMSE指标来对整个训练集的效果进行一个评价

In: from sklearn.metrics import mean_squared_error
In: housing_predictions = lin_reg.predict(housing_prepared)
In: lin_mse = mean_squared_error(housing_labels,housing_predictions)
In: lin_rmse = np.sqrt(lin_mse)
In: lin_rmse
Out: 68628.19819848922

median_housing_values.png

由统计直方图看出,median_housing_values大多数都分布在12000 - 265000之间,因此目前这个lin_reg模型的RMSE达到了68628,这是很不理想的.

这是欠拟合的情况发生了(在训练集上查看出来拟合的效果):

  1. 发生这种情况告诉我们特征并没有提供足够多的信息.
  2. 模型并不足够好

面对这种情况时,我们解决欠拟合的主要方法是:

  1. 挑选一个更加强大的模型,并喂更好的特征
  2. 减少对于模型的约束(比如正则化).

但是我们这个模型并没有正则化,因此只能采取第一种方法进行优化.
我们可以尝试添加更多的特征,比如取人口的对数.
下面尝试训练一种更加强大的回归模型,决策树回归模型DecisionTreeRegressor.这种模型能够发现复杂的非线性关系.

In: from sklearn.tree import DecisionTreeRegressor
In: tree_reg = DecisionTreeRegressor()
In: tree_reg.fit(housing_prepared,housing_labels)
Out: DecisionTreeRegressor(criterion='mse', max_depth=None, max_features=None,
               max_leaf_nodes=None, min_impurity_decrease=0.0,
               min_impurity_split=None, min_samples_leaf=1,
               min_samples_split=2, min_weight_fraction_leaf=0.0,
               presort=False, random_state=None, splitter='best')

In: housing_predicions = tree_reg.predict(housing_prepared)
In: housing_predicions[:5]
Out: array([286600., 340600., 196900.,  46300., 254500.])
In: tree_mse = mean_squared_error(housing_labels,housing_predicions)
In: tree_rmse = np.sqrt(tree_mse)
In: tree_rmse
Out: 0.0

可以发现运用DT模型的时候,这个模型竟然没有error,模型几乎是完美的.但是在训练集上有这样的表现,这往往是严重过拟合的表现.

因此就如前述,对于一个模型而言,千万不能让它去碰测试集.你需要用训练集的一部分去做训练,另一部分做模型验证.因为有些强大的模型能够完美地直接拟合已经训练的数据.

使用交叉验证做出更好的评估

一种评估DT模型的方法就是使用train_test_split方法将训练集分为更小的训练集和验证集,然后用小的训练集和验证集去训练并验证模型.

一种很好的替代方法就是使用scikit-learn的交叉验证功能,这样的验证方法更加可靠.下面的代码演示了K折交叉验证:

  • 它随机将训练集拆分为10个独立的子集,称之为'折'
  • 然后利用10个子集训练并评估10个不同的DT模型
  • 每次都选用一个不同折用于评估,利用剩下的9个折进行训练.
  • 结果是包含10个评估结果的array
In: from sklearn.model_selection import cross_val_score
In: scores =cross_val_score(tree_reg,housing_prepared,housing_labels,scoring='neg_mean_squared_error'
                        ,cv=10)
In: rmse_scores =  np.sqrt(-scores)
In: rmse_scores
Out: array([69166.55059074, 66543.54599875, 71015.98771317, 69322.34433533,
           70674.92662341, 75050.13558149, 70625.34940601, 70823.47513762,
           76224.34231131, 70124.08527976])

注意:scikit-learn交叉验证期望的是一个效用函数(越大越好)而不是损失函数(越小越好),因此得分函数通常是MSE值的负值.这也是为什么在开根之前要先取-scores.

def display_scores(scores):
    print('Scores:',scores)
    print('Mean:',scores.mean())
    print('Standard deviation:',scores.std())

In: display_scores(rmse_scores)

Out:
Scores: [69166.55059074 66543.54599875 71015.98771317 69322.34433533,
70674.92662341 75050.13558149 70625.34940601 70823.47513762,76224.34231131 70124.08527976]
Mean: 70957.07429775785
Standard deviation: 2660.0686304080364

利用了更加科学的方法:'交叉验证'(能真正看出效果),看上去似乎DT的表现并不如先前那么好.事实上,看起来比线性回归模型还要糟糕!
注意到交叉验证不仅让你得到模型性能的评估,并且还能看到模型有多么准确(标准差). 决策树的平均分大概为71400,波动通常在3200上下.如果你只有一个验证集你将得不到这些信息.
但是交叉验证的消耗在于多次训练模型,这样的代价并不是在任何情况下都能接受.

下面计算一下线性回归模型的分数:

In: lin_scores = cross_val_score(lin_reg,housing_prepared,housing_labels,scoring=
                            'neg_mean_squared_error',cv=10)
In: lin_rmse_scores = np.sqrt(-lin_scores)
In: display_scores(lin_rmse_scores)
Out: Scores: [66782.73843989 66960.118071   70347.95244419 74739.57052552
     68031.13388938 71193.84183426 64969.63056405 68281.61137997 71552.91566558 67665.10082067]
    Mean: 69052.46136345083
    Standard deviation: 2731.674001798348

可以看到DT模型过拟合验证集,表现甚至是要比线性回归模型更差的

接下来尝试最后一个模型:随机森林.
随机森林的原理是用随机的特征子集训练大量的DT模型.然后基于大量的DT模型做出决策.这样的思路是提升ML算法性能的一个重要方法.

In: from sklearn.ensemble import RandomForestRegressor
In: forest_reg = RandomForestRegressor()
In: forest_reg.fit(housing_prepared,housing_labels)
In: forest_score = cross_val_score(forest_reg,housing_prepared,housing_labels,scoring=
                               'neg_mean_squared_error',cv=10)
In: forest_rmse_scores = np.sqrt(-forest_score)
In: display_scores(forest_rmse_scores)
Out: Scores: [52139.11827562 49499.22947406 52429.83310745 55613.43640446
     52186.19472032 56245.76235768 51450.03759208 51103.18141115
     56223.7137723  53716.20236911]
    Mean: 53060.67094842296
    Standard deviation: 2195.8523393951145

计算随机森林的rmse得分:

In: housing_predicions = forest_reg.predict(housing_prepared)
In: forest_rmse_scores = np.sqrt(mean_squared_error(housing_labels,housing_predicions))
In: forest_rmse_scores
Out: 22723.911950422345

我们可以看到随机森林的效果提升明显,是一个很有希望的方法.但是训练集上的结果依然比验证集上的得分小非常多,这说明模型依然是过拟合的.

为了解决过拟合,可以采取以下的措施:

  1. 简化模型,采用正则化的方式限制模型
  2. 使用更多的训练数据(防止过拟合,被部分数据带偏)

在深入随机森林之前,你应该尝试下机器学习算法的其它类型模型(不同核心的支持向量机,神经网络,等等),不要在调节超参数上花费太多时间。目标是列出一个可能模型的列表(两到五个)。

模型微调

假定你已经有了好几个有希望的模型,那么你需要如何去调整优化它们呢?

<简书不支持html这种标记方式>网格搜索(调参神器) <我能怎么办,我只能手动标注重点了 😃 >

  1. 手动调参将是不现实也将是非常耗时且乏味的.
  2. scikit-learn提供了网格搜索GrideSearchCV方法为我们做参数搜索的工作.我们需要做的就是传入我们想要试验的超参数,参数值,该方法会使用交叉验证的方法评估所有可能的超参数的组合.

下面测试使用网格搜索为随机森林搜寻最佳的超参数组合

from sklearn.model_selection import GridSearchCV
param_grid = [{'n_estimators':[3,10,30],'max_features':[2,4,6,8]},
             {'bootstrap':[False],'n_estimators':[3,10],'max_features':[2,3,4]}]
forest_reg = RandomForestRegressor()
grid_search = GridSearchCV(forest_reg,param_grid,cv=5,scoring='neg_mean_squared_error')

注: bootstrap 超参数指定了是否是有放回的采样,默认值是True即有放回.
注意:随机采样(bootsrap)就是从我们的训练集里面采集固定个数的样本,但是每采集一个样本后,都将样本放回。也就是说,之前采集到的样本在放回后有可能继续被采集到。

param_grid传入的grid_search是需要测定18种参数组合的,而每一种参数组合模型是会训练5次的(用了5折交叉验证,cv=5),换句话说将会有18 * 5 = 90次训练,也就是将有90个模型被生成评估.这是比较耗时的,但是完成的时候你可以拿到最好的超参数组合.

In: grid_search.fit(housing_prepared,housing_labels)
Out: GridSearchCV(cv=5, error_score='raise-deprecating',
           estimator=RandomForestRegressor(bootstrap=True, criterion='mse', max_depth=None,
               max_features='auto', max_leaf_nodes=None,
               min_impurity_decrease=0.0, min_impurity_split=None,
               min_samples_leaf=1, min_samples_split=2,
               min_weight_fraction_leaf=0.0, n_estimators='warn', n_jobs=None,
               oob_score=False, random_state=None, verbose=0, warm_start=False),
           fit_params=None, iid='warn', n_jobs=None,
           param_grid=[{'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]}, {'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]}],
           pre_dispatch='2*n_jobs', refit=True, return_train_score='warn',
           scoring='neg_mean_squared_error', verbose=0)

如果你不能确定超参数该用什么比较好,一个简单的方法就是使用连续的10的幂的数(如果想要进行更小粒度的搜索,也可以采用更小的数,例如本例中的n_estimators)

In: grid_search.best_params_
Out: {'max_features': 6, 'n_estimators': 30}

可以看到n_estimators的最佳参数是30(我们列举的最大的值),因此此时我们可以尝试将n_estimators更大的值列入搜寻的范围,可能会有更好的效果.

我们也已经拿到了最好的预测器

In: grid_search.best_estimator_
Out: RandomForestRegressor(bootstrap=True, criterion='mse', max_depth=None,
               max_features=6, max_leaf_nodes=None, min_impurity_decrease=0.0,
               min_impurity_split=None, min_samples_leaf=1,
               min_samples_split=2, min_weight_fraction_leaf=0.0,
               n_estimators=30, n_jobs=None, oob_score=False,
               random_state=None, verbose=0, warm_start=False)

查看每个参数组合下的模型的得分

cvres = grid_search.cv_results_
for mean_score,params in zip(cvres['mean_test_score'],cvres['params']):
    print(np.sqrt(-mean_score), params)
Out:
    63612.103000118135 {'max_features': 2, 'n_estimators': 3}
    55711.8198772516 {'max_features': 2, 'n_estimators': 10}
    52854.67439984241 {'max_features': 2, 'n_estimators': 30}
    59625.31171120096 {'max_features': 4, 'n_estimators': 3}
    53215.95533215571 {'max_features': 4, 'n_estimators': 10}
    50325.738110031445 {'max_features': 4, 'n_estimators': 30}
    59266.62134036455 {'max_features': 6, 'n_estimators': 3}
    52476.67607546813 {'max_features': 6, 'n_estimators': 10}
    49804.8632562366 {'max_features': 6, 'n_estimators': 30}
    58402.64862222165 {'max_features': 8, 'n_estimators': 3}
    52316.6376868916 {'max_features': 8, 'n_estimators': 10}
    50030.72214217435 {'max_features': 8, 'n_estimators': 30}
    62645.29839193287 {'bootstrap': False, 'max_features': 2, 'n_estimators': 3}
    54350.05441732232 {'bootstrap': False, 'max_features': 2, 'n_estimators': 10}
    59667.78209721833 {'bootstrap': False, 'max_features': 3, 'n_estimators': 3}
    52949.122929664874 {'bootstrap': False, 'max_features': 3, 'n_estimators': 10}
    58383.95829180255 {'bootstrap': False, 'max_features': 4, 'n_estimators': 3}
    51925.63083312882 {'bootstrap': False, 'max_features': 4, 'n_estimators': 10}

使用{'max_features': 6, 'n_estimators': 30}参数组合能够取得49804的成绩,相比之前的53060已经有了不小的提升.模型微调是成功的.

<手动强调>
当然不能忘记:

  1. 可以把某些数据准备步骤做的调整当做是超参数.可以在网格搜索中自动地搜寻判断最佳的特征组合方案,测试一些你不太清楚效果的特征.
  2. 同样也可以用来自动搜寻处理离群值,缺失值和特征选择等繁琐的操作.

<手动强调>

随机搜索(其实是一种更加推荐的方法)

(https://blog.csdn.net/juezhanangle/article/details/80051256) 还不错介绍的文章
当你搜寻相对少的组合的时候,grid search还是比较ok的,就像前述的例子一样. 但是超参数的搜寻域是非常大的,也就是不可能实现大范围的搜索遍历.
这种时候就需要随机搜索RandomizedSearchCV的支持.

  1. 不再是搜寻所有可能组合的思路
  2. 在每一次迭代时都通过为超参数挑选随机数值的方式来评估给定数量个随机参数组合模型.
  3. 优势在于:
    • 随机搜索时,如果运行1000次,它会为每个超参数探索1000个不同值,而不像grid search那样只针对特定的几个超参数进行搜寻.
    • 你可以更好地通过设定迭代次数来控制计算开销.

集成方法

另外一个调整系统的方法就是讲表现较好的模型进行组合,从而期望得到一个表现更好的模型.这种'ensemble'思路下的模型通常要比最好的单个模型表现都要好.

分析最佳模型以及它们的误差

通过分析最佳模型,我们通常能够对问题有更深入的了解.
比如RandomForestRegressor能够指示出每个参数对做出最准确预测的相对重要性:

In: feature_importance = grid_search.best_estimator_.feature_importances_
In: feature_importance
Out: array([7.41664388e-02, 7.07907175e-02, 4.28625371e-02, 1.79719493e-02,
           1.62705597e-02, 1.86339608e-02, 1.61629091e-02, 3.75001961e-01,
           4.39887397e-02, 1.07949019e-01, 5.90690293e-02, 1.08288098e-02,
           1.38722570e-01, 8.04572063e-05, 2.69477214e-03, 4.80556934e-03])

我们可以将这些重要的分数和对应的属性名称放到一起:

extra_attribs = ['room_per_hhold','pop_per_hhold','bedrooms_per_room']
cat_one_hot_attribs = list(encoder.classes_)
attributes = num_attribs + extra_attribs + cat_one_hot_attribs
sorted(zip(feature_importance, attributes),reverse=True)
Out:
     [(0.3750019612559819, 'median_income'),
     (0.13872256966756555, 'INLAND'),
     (0.1079490192331523, 'pop_per_hhold'),
     (0.07416643880806785, 'longitude'),
     (0.07079071748646316, 'latitude'),
     (0.05906902934048595, 'bedrooms_per_room'),
     (0.04398873969400665, 'room_per_hhold'),
     (0.04286253709600471, 'housing_median_age'),
     (0.01863396082921661, 'population'),
     (0.017971949317441203, 'total_rooms'),
     (0.016270559681538607, 'total_bedrooms'),
     (0.016162909131536056, 'households'),
     (0.010828809777764731, '<1H OCEAN'),
     (0.004805569337738232, 'NEAR OCEAN'),
     (0.002694772136742528, 'NEAR BAY'),
     (8.045720629387519e-05, 'ISLAND')]

手动强调
有了这个信息,你就可以丢弃一些不那么有用的特征(比如,显然只要一个ocean_proximity分类就够了,即OCEAN与否,所以可以丢弃掉其它的分类诸如NEAR OCEAN / NEAR BAY / ISLAND)。你还应该看一下系统犯的误差,搞清为什么会有些误差,以及如何改正问题(添加更多的特征,或相反,去掉没有什么信息的特征,清洗异常值等等)。

同时你还可以查看你的系统犯的一些具体的错误,然后尝试去理解为什么会犯错并尝试去修复这些问题(比如添加额外的特征,取反,去掉一些没有用的特征,清除离群值等等)
:-)

在测试集上评估你的模型

在微调模型后,我们已经拿到了一个表现足够不错的模型.
现在可以将这个最终模型用到测试集上进行评估.
在这一步需要对测试集进行一定的拆分 + 变形处理.但是要注意的是:这里的变形处理用的是transform(),而非fit_transform(),具体的缘由在前面已经讲过了

指定网格搜索的结果为最终的模型

In: final_model = grid_search.best_estimator_
In: X_test = strat_test_set.drop('median_house_value',axis=1)
In: y_test = strat_test_set['median_house_value'].copy()
In: X_test_prepared = full_pipeline.transform(X_test)
In: final_prediciton = final_model.predict(X_test_prepared)
In: final_mse = mean_squared_error(y_test,final_prediciton)
In: final_rmse = np.sqrt(final_mse)
In: final_rmse
Out: 48249.82755755361

可以看到我们的模型利用随机森林达到的最终效果为 48249.8

手动强调:
这个final_rmse的结果通常要比我们交叉验证的结果要差一点点(因为我们的模型在验证集上经过调整,它会在陌生的集合上表现的不如在验证集上那么好).这种情况是比较正常的,你不能因为该模型在测试集上表演稍差一点就根据测试集对模型进行参数调整,这样的调参依据通常是不现实的,因为即使有所提升也不会体现到新的数据上去.

尝试使用Xgboost

In: from xgboost import XGBRegressor
In: xgboost_reg = XGBRegressor()
In: xgboost_reg.fit(housing_prepared,housing_labels)
Out: XGBRegressor(base_score=0.5, booster='gbtree', colsample_bylevel=1,
           colsample_bytree=1, gamma=0, learning_rate=0.1, max_delta_step=0,
           max_depth=3, min_child_weight=1, missing=None, n_estimators=100,
           n_jobs=1, nthread=None, objective='reg:linear', random_state=0,
           reg_alpha=0, reg_lambda=1, scale_pos_weight=1, seed=None,
           silent=True, subsample=1)

In: xgboost_scores = cross_val_score(xgboost_reg,housing_prepared,housing_labels,
                                scoring = 'neg_mean_squared_error',cv=10)

In: xgboost_rmse_scores = np.sqrt(-xgboost_scores)

In: display_scores(xgboost_rmse_scores)
Out:
    Scores: [52056.36777099 49897.25202193 52875.58279806 55219.63304289
    53215.38148905 56019.44318581 50929.49959547 50775.14967982 56506.79613124 53597.20486028]
    Mean: 53109.231057552926
    Standard deviation: 2153.951090593369

In: xgboost_prediciton = xgboost_reg.predict(housing_prepared)
In: xgboost_rmse = np.sqrt(mean_squared_error(housing_labels,xgboost_prediciton))
In: xgboost_rmse
Out: 50467.191745053715

可以看到Xgboost在未调参的情况下已经有了不错的性能,并且由xgboost_rmse看出并没有严重地过拟合现象发生

下面尝试利用RandomizedSearchCV对xgboost模型优化参数

from sklearn.model_selection import RandomizedSearchCV
param_dist={
    'n_estimators':range(80,200,4),
    'max_depth':range(2,15,1),
    'learning_rate':np.linspace(0.01,2,20),
    'subsample':np.linspace(0.7,0.9,20),
    'colsample_bytree':np.linspace(0.5,0.98,10),
    'min_child_weight':range(1,9,1)
}
grid_search =  RandomizedSearchCV(xgboost_reg,param_dist,n_iter=300,cv=10,scoring='neg_mean_squared_error',n_jobs = -1)

进行了对xgboost的随机搜索,迭代周期较长,下面选择略去,模型保存在了ML_model中

In: grid_search.fit(housing_prepared,housing_labels)
In: grid_search.best_estimator_.feature_importances_
Out: array([0.14419934, 0.13962418, 0.08169935, 0.06437909, 0.04607843,
           0.05400327, 0.03316994, 0.11683007, 0.09428105, 0.11503268,
           0.08480392, 0.00833333, 0.01004902, 0.        , 0.00277778,
           0.00473856], dtype=float32)
In: grid_search.best_params_
Out:{'subsample': 0.8368421052631578,
     'n_estimators': 156,
     'min_child_weight': 3,
     'max_depth': 7,
     'learning_rate': 0.11473684210526315,
     'colsample_bytree': 0.9266666666666666}
In: grid_search.best_estimator_
Out: XGBRegressor(base_score=0.5, booster='gbtree', colsample_bylevel=1,
           colsample_bytree=0.9266666666666666, gamma=0,
           learning_rate=0.11473684210526315, max_delta_step=0, max_depth=7,
           min_child_weight=3, missing=None, n_estimators=156, n_jobs=1,
           nthread=None, objective='reg:linear', random_state=0, reg_alpha=0,
           reg_lambda=1, scale_pos_weight=1, seed=None, silent=True,
           subsample=0.8368421052631578)

In: xgboost_reg_final = grid_search.best_estimator_

为了重复训练耗费时间,我们将训练好的模型存储起来

In: from sklearn.externals import joblib
In: import os
In: os.chdir('C:/Users/Administrator/ML_model')
In: joblib.dump(xgboost_reg_final,'Canifornia_house_price_xgboostModel.pkl')
Out:['Canifornia_house_price_xgboostModel.pkl']
In: xgboost_reg_final_predictions = xgboost_reg_final.predict(X_test_prepared)
In: final_mse_xgboost = mean_squared_error(y_test,xgboost_reg_final_predictions)
In: final_rmse_xgboost = np.sqrt(final_mse_xgboost)
In: final_rmse_xgboost
Out: 44216.3972564512    # 这样的成绩已经比较不错了

我们可以看到运用Xgboost进行建模,对结果有了较大的提升.

最后附上一张思维导图(放大可以清楚查看):

house_price_California.png

posted @ 2020-07-09 23:54  seekerJunYu  阅读(413)  评论(0编辑  收藏  举报