机器学习实战(基于Scikit-Learn和TensorFlow)(1)

一、下载数据

import os
import tarfile
from six.moves import urllib
DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml/master/"
HOUSING_PATH = "datasets/housing"
HOUSING_URL = DOWNLOAD_ROOT + HOUSING_PATH + "/housing.tgz"
def fetch_housing_data(housing_url=HOUSING_URL,housing_path=HOUSING_PATH):
    if not os.path.isdir(housing_path):
        os.makedirs(housing_path)
    tgz_path = os.path.join(housing_path,"housing.tgz")
    urllib.request.urlretrieve(housing_url,tgz_path)
    housing_tgz=tarfile.open(tgz_path)
    housing_tgz.extractall(path=housing_path)
    housing_tgz.close()
fetch_housing_data()

现在,每当你调用fetch_housing_data(),会自动在工作区创建一个datasets/housing目录,然后下载housing.tgz文件,并将housing.csv解压到这个目录。

现在用pandas加载数据:

import pandas as pd
def load_housing_data(housing_path=HOUSING_PATH):
    csv_path=os.path.join(housing_path,"housing.csv")
    return pd.read_csv(csv_path)

这个函数会返回一个包含所有数据的Pandas DataFrame对象。

二、快速查看数据结构

使用DataFrame的head()方法查看前五行:

housing = load_housing_data()
housing.head()

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第1张图片

每一行代表一个区,总共有10个属性:longitude,latitude,housing_median_age,total_rooms,total_bed_rooms,population,households,meidan_income,

median_house_value以及ocean_prroximity

通过info()方法可以快速捕获数据集的简单描述,特别是总行数,每个属性的类型和非空值的数量。

housing.info()

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第2张图片使用value_counts()方法查看有多少种分类存在,每种类别下分别有多少区域:

housing["ocean_proximity"].value_counts()

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第3张图片

housing.describe()

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第4张图片

另一种快速了解数据类型的方法是绘制每个数值属性的直方图。直方图用来显示给定值的范围(横轴)的实例数量(纵轴)。你可以一次绘制一个属性,也可以在整个数据集上调用hist()方法,绘制每个属性的直方图。

import matplotlib.pyplot as plt
housing.hist(bins=50,figsize=(20,15))
plt.show()

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第5张图片三、创建测试集

理论上,创建测试集非常简单:只要随机选择一些实例,通常是数据集的20%,然后将它们放在一边:

import numpy as np
def split_train_test(data,test_ratio):
    shuffled_indices = np.random.permutation(len(data))
    test_set_size = int(len(data)*test_ratio)
    test_indices = shuffled_indices[:test_set_size]
    train_indices = shuffled_indices[test_set_size:]
    return data.iloc[train_indices], data.iloc[test_indices]
train_set, test_set = split_train_test(housing, 0.2)
print(len(train_set), "train+", len(test_set), "test")

但这样子并不完美:如果在运行一遍,它会产生一个不同的数据集!

解决方案有两个:1.在第一次运行程序之后即保存测试集,随后的运行只是加载它而已

                               2.在调用np.random.permutation()之前设置一个随机数生成器的种子(例如,np.random.seed(42)),从而让他始终生成相同的碎机索引。

但是这两种方法在获得下一次更新的数据时都会中断。

解决办法:每个实例都使用一个标识符(identifier)来决定是否进入测试集(假定每个实例都有一个唯一且不变的标识符)。

举例来说:可以计算每个实例标识符的hash值,只取hash的最后一个字节如果该值小于等于51(约256的20%),则将实例放入测试集中。

import hashlib
def test_set_check(identifier, test_ratio, hash):
    return hash(np.int64(identifer)).digest()[-1]<256*test_ratio
def split_train_test_by_id(data,test_ratio,id_column,hash=hashlib.md5):
    ids=data[id_column]
    in_test_set=ids.apply(lambda id_: test_set_check(id_,test_ratio,hash))
    return data.loc[~in_test_set],data.loc[in_test_set]
housing_with_id = housing.reset_index()  #adds an 'index'column
train_set, test_set = split_train_test_by_id(housing_with_id,0.2,"index")

如果使用行索引作为唯一标识符,你需要确保在数据集的末尾添加新数据,并且不会删除任何行。如果不能保证这点,可以尝试使用某个最稳定的特征来创建唯一的标识符。例如一个地区的经纬度:

housing_with_id["id"] = housing["longitude"]*1000 + housing["latitude"]
train_set, test_set = split_train_test_by_id(housing_with_id,0.2,"id")

Scikit-Learn提供了一些函数,可以通过多种方式将数据集分成多个子集。

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

分层抽样:将人口划分为均匀的子集,每个子集被称为一层,然后从每层抽取正确的实例数量,以确保测试集和代表了总的人口比例。

创建收入类别属性:

将收入中位数除以1.5(限制收入类别的数量),然后使用ceil进行取整(得到离散类别),然后将所有大于5的类别合并为类别5:

housing["income_cat"] = np.ceil(housing["median_income"]/1.5)
housing["income_cat"].where(housing["income_cat"]<5,5.0, inplace=True)

现在可以根据收入类别进行分层抽样,使用Scikit-Learn的Stratified Shuffle Split类:

from sklearn.model_selection import StratifiedShuffleSplit
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_cat"].value_counts()/len(housing)

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第6张图片

现在可以删除income_cat属性,将数据恢复原样了:

for set in (strat_train_set,strat_test_set):
    set.drop(["income_cat"],axis=1,implace=True)

四、从数据探索和可视化中获取洞见

先创建一个副本,可以随意尝试而不损害训练集:

housing = strat_train_set.copy()

将地理数据可视化

由于存在地理位置信息(经度和纬度),因此可以建立一个各区域的分布图以便数据可视化:

housing.plot(kind="scatter",x="longitude",y="latitude")

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第7张图片

高密度区域的可视化:

housing.plot(kind="scatter",x="longitude",y="latitude",alpha=0.1)

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第8张图片

再看看房价,每个圆的半径大小代表了每个地区的人口数量(选项s),颜色代表价格(选项c)。使用一个名叫jet的预定义颜色表(选项cmap)来进行可视化,颜色范围从蓝(低)到红(高):

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,
)
plt.legend()

加利福尼亚州房屋价格:

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第9张图片

这张图片可以看出房屋价格与地理位置和人口密度息息相关。

一个常用的方法:使用聚类算法来检测主群体,然后再为各个聚类中心添加一个新的衡量邻近距离的特征。

五、寻找相关性

由于数据集不大,可以采用corr()方法轻松计算出每对属性之间的标准相关系数(皮尔逊相关系数):

corr_matrix = housing.corr()

可以看看每个属性与房屋中位数的相关性分别是多少:

corr_matrix["median_house_value"].sort_values(ascending=False)

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第10张图片

相关系数的范围从-1到+1,越接近+1,表示有越强的正相关;比如,当收入中位数上升时,房价中位数也趋于上升。当系数接近-1,则表示有强烈的负相关;注意看纬度和房价中位数之间呈现出轻微的负相关(也就是说,越往北走,房价倾向于下降)。最后,系数靠近0则说明二者之间没有线性相关性。

还有一种方法可以检测属性之间的相关性,就是使用pandas的scatter_matrix函数,它会绘制出每个数值属性相对于其他属性的相关性。

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

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第11张图片

最具潜力能够预测房价中位数的属性是收入中位数,放大收入中位数来看看其相关性散点图:

housing.plot(kind="scatter",x="median_income",y="median_house_value",alpha=0.1)

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第12张图片

上图说明几个问题:

1.二者相关性确实很强,可以清晰的看到上升的趋势,并且点也不是太分散。

2.前面提到的50万美元的价格上限在图中是一条清晰的水平线,不过除此以外,图上还有几条不太明显的直线,45万、35万、28万等,再往下可能还有一些,避免以后重现这些怪异的数据,可以尝试删除。

试验不同属性组合:

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"]
corr_matrix = housing.corr()
corr_matrix["median_house_value"].sort_values(ascending=False)

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第13张图片新的属性bedrooms_per_room较之“房间总数”或是“卧室总数”与房价中位数的相关性都要高得多。显然卧室/房屋比例更低的房屋,往往价格更贵。同样“每个家庭的房间数量”也比“房间总数”更具信息量--------房屋越大,价格越贵。

六、机器学习算法的数据准备

先回到一个干净的数据集(再次复制strat_train_set),然后将预测器和标签分开,因为这里我们不一定对它们使用相同的转换方式(需要注意drop()会创建一个数据副本,但是不影响strat_train_set):

housing = strat_train_set.drop("median_house_value",axis=1)
housing_label = strat_train_set["median_house_value"].copy()

数据清理

大部分的机器学习算法无法在缺失的特征上工作,因此要创建一些函数来辅助它。有以下三种选择:

1.放弃这些相应的地区

2.放弃这个属性

3.将缺失的值设置为某个值(0、平均数或者是中位数等都可以)

通过DataFrame的dropna()、deop()、fillna()方法,可以轻松完成这些操作:

housing.dropna(subset=["total_rooms"])         #option 1
housing.drop("total_bedrooms",axis=1)          #option 2
median = housing["total_bedrooms"].median()
housing["total_bedrooms"].fillna(median)       #option 3

Scikit-Learn提供了一个非常容易上手的教程来处理缺失值:imputer。使用方法如下,首先,需要创建一个imputer实例,指定要用属性的中位数值替代该属性的缺失值:

from sklearn.preprocessing import Imputer
imputer = Imputer(strategy = "median")

由于中位数值只能在数值属性上计算,所以需要创建一个没有文本属性的数据副本ocean_proximity

housing_num = housing.drop("oceans_proximity",axis=1)

使用fit()方法将imputer实例适配到训练集:

imputer.fit(housing_num)

这里imputer仅仅是计算了每个属性的中位数值,并将结果存储在其实例变量statistics_中。虽然只有total_bedrooms这个属性存在缺失值,但是无法确认系统启动之后新数据中是否一定不存在任何缺失值,所以稳妥起见,将imputer应用于所有的数值属性:

>>>imputer.statistics_
array([-118.51 , 34.26 , 29. , 2119. , 433. , 1164. , 408. , 3.5414])
>>>housing_num.median().values
array([-118.51 , 34.26 , 29. , 2119. , 433. , 1164. , 408. , 3.5414])

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第14张图片

现在,可以使用这个“训练有素”的imputer将缺失值替换成中位数值完成训练集替换:

X=imputer.transform(housing_num)

结果是一个包含转换后特征的Numpy数组。如果想将它放回Pandas DataFrame,也很简单:

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

处理文本和分类属性

之前排除了分类属性ocean_proximity,因为它是一个文本属性,无法计算它的中位值。

Scikit-Learn提供了一个转换器LabelEncoder:

from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()
housing_cat = housing["ocean_proximity"]
housing_cat_encoded = encoder.fit_transform(housing_cat)
print(housing_cat_encoded)
print(encoder.classes_)

输出的数字编码与编码对应的类型为:

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第15张图片

但这种方法产生的问题是:机器学习算法会以为两个相近的数字比两个离得较远的数字更为相似一些,显然真实情况并非如此(比如,类别0和类别4之间就比类别0和类别1之间的相似度更高)。

为了解决这个问题,Scikit-Learn提供了一个OneHotEncoder编码器,可以将整数分类值转换为独热向量(机器学习:数据预处理之独热编码(One-Hot)),值得注意的是,fit_transform()需要一个二维数组,但是housing_cat_encoded是一个一位数组,所以需要将它重塑:

from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder()
housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.reshape(-1,1))
housing_cat_1hot

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第16张图片注意这里的输出是一个SciPy稀疏矩阵,而不是一个NumPy数组,要转换成NumPy数组只需要调用toarray()方法即可:

housing_cat_1hot.toarray()

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第17张图片

使用LabelBinarizer类可以一次性完成两个转换(从文本类别转化为整数类别,再从整数类别转换为独热向量):

from sklearn.preprocessing import LabelBinarizer
encoder = LabelBinarizer()
housing_cat_1hot = encoder.fit_transform(housing_cat)
housing_cat_1hot

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第18张图片

自定义转换器

虽然Scikit-Learn提供了不少有用的转换器,但是你还需要为一些特定的清理操作或是组合特定属性的任务编写自己的转换器,自定义的转换器与Scikit-Learn无缝链接需要创建一个类,然后应用以下三个方法:fit()(返回自身)、transform()、fit_transform()。下面一个例子,用来添加组合后的属性:

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

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]
        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,
                         bedrooms_per_room]
        else:
            return np.c_[X, rooms_per_household, population_per_household]

attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)
housing_extra_attribs = attr_adder.transform(housing.values)

特征缩放

如果输入数值属性具有非常大的比例差异,往往导致机器学习算法的性能表现不佳。

同比例缩放所有属性,常用的两种方法是:最小-最大缩放和标准化。

最小-最大缩放(又称作归一化)很简单:将值重新缩放使其最终范围归于0~1之间,实现方法是将值减去最小值并除以最大值和最小值的差,对此Scikit-Learn提供了一个名为MinMaxScaler的转换器

标准化:首先减去平均值(所以标准化的均值总是零),然后除以方差,从而使得结果的分布具有单位方差。Scikit-Learn提供了一个标准化的转换器StandadScaler。

转换流水线

许多数据转换的步骤需要以正确的顺序来执行,而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()),
   ])
housing_num_tr = num_pipeline.fit_transform(housing_num)

Pipeline构造函数会通过一系列名称/估算器的配对来定义步骤的序列。除了最后一个是估算器之外,前面都必须是转换器(也就是说必须有fit_transform()方法)。

当调用流水线的fit()方法时,会在所有的转换器上按照顺序依次调用fit_transform(),将一个调用的输出作为参数传递给下个调用方法,直到传递到最终的估算器,则只会调用fit()方法。

一个完整的处理数值和分类属性的流水线可能如下所示:

from sklearn.pipeline import FeatureUnion

from sklearn.base import BaseEstimator, TransformerMixin
class LabelBinarizer_new(TransformerMixin, BaseEstimator):
    def fit(self, X, y = 0):
        self.encoder = None
        return self
    def transform(self, X, y = 0):
        if(self.encoder is None):
            print("Initializing encoder")
            self.encoder = LabelBinarizer();
            result = self.encoder.fit_transform(X)
        else:
            result = self.encoder.transform(X)
        return result
    
num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]

num_pipeline = Pipeline([
    ('selector', DataFrameSelector(num_attribs)),
    ('imputer', Imputer(strategy="median")),
    ('attribs_adder', CombinedAttributesAdder()),
    ('std_scaler', StandardScaler()),    
])
cat_pipeline = Pipeline([
    ('selector',DataFrameSelector(cat_attribs)),
    ('label_binarizer',MyLabelBinarizer()),
])
full_pipeline = FeatureUnion(transformer_list=[
    ("num_pipeline",num_pipeline),
    ("cat_pipeline",cat_pipeline),
])

运行整条流水线:

housing_prepared = full_pipeline.fit_transform(housing)
housing_prepared

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第19张图片

housing_prepared.shape

注意:按照书中的代码使用sklearn的Pipeline时会出现如下错误:

原因:

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第20张图片

解决办法:Scikit-Learn报错Error message: fit_transform() takes 2 positional arguments but 3 were given的解决方法

自己包装一个可以传入三个参数的自定义的LabelBinarizer类:

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)
#Keep your code the same only instead of using LabelBinarizer(), use the class we created : MyLabelBinarizer().

七、选择和训练模型

在框出问题,获得数据,进行数据探索,并对训练集和测试集进行抽样同时编写了转换流水线,从而可以自动清洗和准备机器学习算法的数据,进行选择机器学习模型和开展训练了。

培训和评估训练集

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

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

from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared,housing_labels)
some_data = housing.iloc[:5]
some_labels = housing_labels.iloc[:5]
some_data_prepared = full_pipeline.transform(some_data)
print("Predictions:\t",lin_reg.predict(some_data_prepared))
print("Labels:\t\t",list(some_labels))

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第21张图片

可以工作了,但预测不是很准确,可以使用Scikit-Learn的mean_squared_error函数来预测整个训练集上回归模型的RMSE:

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

由此看出:大多数地区的median_housing_values分布在120000到265000美元之间,所以典型的预测误差达到68376美元只能说是差强人意了。这就是典型的预测模型对训练数据拟合不足的案例。这种情况的发生通常意味着这些特征可能无法提供一个足够的信息来做出更好的预测,或者是模型本身不够强大。

尝试一个更复杂的模型试试看:

DecisonTreeRegressor,它可以从数据中找到复杂的非线性关系:

from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor()
tree_reg.fit(housing_prepared,housing_labels)
housing_predictions = tree_reg.predict(housing_prepared)
tree_mse = mean_squared_error(housing_labels,housing_predictions)
tree_rmse = np.sqrt(tree_mse)
tree_rmse

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第22张图片

从结果来看没有错误,但模型真的可以做到这么完美吗?显然是过拟合了,那么如何确认呢?前面提到除非你有信心启动模型,否则不要触碰测试集,在这里将训练集的一部分用于训练,另一部分用于模型的验证。

使用交叉验证来更好的评估

评估决策树模型的一种方法是使用train_test_split函数将训练集分成较小的训练集和验证集,然后根据这些较小的训练集来训练模型,并对其进行评估。

另一个不错的选择是使用Scikit-Learn的交叉验证功能 。以下是执行K-折(K-fold)交叉验证的代码:它将训练集随机分割成10个不同的子集,每个子集成为一个折叠(fold),然后对决策树模型进行10次训练和评估----每次挑选一个折叠进行评估,使用另外的9个折叠进行训练。产出的结果是一个包含10次评估分数的数组:

from sklearn.model_selection import cross_val_score
tree_scores = cross_val_score(tree_reg,housing_prepared,housing_labels,scoring="neg_mean_squared_error",cv=10)
lin_scores = cross_val_score(lin_reg,housing_prepared,housing_labels,scoring="neg_mean_squared_error",cv=10)
tree_rmse_scores = np.sqrt(-tree_scores)
lin_rmse_scores = np.sqrt(-lin_scores)

def display_scores(scores):
    print("Scores:",scores)
    print("Mean:",scores.mean())
    print("Standard deviation:",scores.std())
display_scores(tree_rmse_scores)
display_scores(lin_rmse_scores)

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第23张图片

从上面的比较可以看出,决策树模型确实严重过度拟合了,以至于表现的比线性回归模型还要糟糕。

再试试随机森林RandomForestRegressor:

from sklearn.ensemble import RandomForestRegressor
forest_reg = RandomForestRegressor()
forest_reg.fit(housing_prepared,housing_labels)
housing_predictions = forest_reg.predict(housing_prepared)
forest_mse = mean_squared_error(housing_labels,housing_predictions)
forest_rmse = np.sqrt(forest_mse)
forest_rmse
from sklearn.model_selection import cross_val_score
forest_scores = cross_val_score(forest_reg,housing_prepared,housing_labels,scoring="neg_mean_squared_error",cv=10)
forest_rmse_scores = np.sqrt(-forest_scores)
def display_scores(scores):
    print("Scores:",scores)
    print("Mean:",scores.mean())
    print("Standard deviation:",scores.std())
display_scores(forest_rmse_scores)

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第24张图片

从上图运行结果来看,随机森林似乎很适合,但训练集上的分数仍然高于验证集,这就意味着该模型对训练姐过度拟合。过度拟合的方案包括简化模型、约束模型(即使其正规化),或者是获得更多的数据。

八、微调模型

假设已经有了一个有效模型的候选列表,现在需要对他们进行微调,下面是几个可行的方法:

网格搜素

用Scikit-Learn的GridSearchCV来进行探索,告诉它你要进行试验的超参数是什么,以及要尝试的值,它会使用交叉验证来评估超参数的所有可能的组合。

例如,下面这段代码搜素RandomForestRegressor的超参数值的最佳组合:

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')
grid_search.fit(housing_prepared,housing_labels)

这个param_grid告诉Scikit-Learn,首先评估第一个dict中的n_estimator和max_features的所有3*4=12种超参数值组合,接着,尝试第二个dict中超参数值的所有2*3=6种组合,但这次超参数bootstrap需要设置为False而不是True(True是该超参数的默认值)。总之,网格搜索将探索RandomForestRegressor超参数值的12+6=18种组合,并对每个模型进行五次训练(使用的是5-折交叉验证),也就是会完成18*5=90次训练,得到的最佳参数组合是:

grid_search.best_params_

还可以直接得到最好的估算器:

grid_search.best_estimator_

如果GridSearchCV被初始化为refit=True(这也是默认值),那么一旦通过交叉验证找到了最佳估算器,它将在整个训练集上重新训练。

当然还有评估分数:

cvres = grid_search.cv_results_
for mean_score,params in zip(cvres["mean_test_score"],cvres["params"]):
    print(np.sqrt(-mean_score),params)

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第25张图片

在本例中,得到的最佳解决方案是将超参数max_features设置为6,n_estimators设置为30.这个组合的RMSE分数为51150,略高于之前使用默认超参数值的分数53029,因此成功的将模型调整到了最佳模式。

随机搜索

如果探索的组合数量较少-----例如上一个示例,网格搜索是一个不错的方案;但是当超参数的搜索范围(search space)较大时,通常会优先选择使用RandomizedSearchCV。这个类用起来与GridSearchCV类大致相同,但他不会尝试所有可能的组合,而是在每次迭代中为每个超参数选择一个随机值,然后对一定数量的随机组合进行评估。这个方法有两个显著特性:

1、如果运行随机搜索1000个迭代,那么将会探索每个超参数的1000个不同的值(而不是像网格搜素方法那样每个超参数仅探索少量几个值)

2、通过简单的设置迭代次数,可以更好的控制要分配给探索的超参数的计算预算。

集成方法

还有一种微调系统的方法是将表现最优的模型组合起来。组合(或集成)方法通常比最佳的单一模型更好(就像随机森林比其所依赖的任何单个决策树模型更好一样),特别是当单一模型会产生严重不同类型的错误时更是如此。

分析最佳模型及其错误

通过检查最佳模型,你可以得到一些好的洞见。例如在进行准确预估时,RandomForestRegressor可以指出每个属性的相对重要程度:

feature_importances = grid_search.best_estimator_.feature_importances_
feature_importances

将这些重要性分数显示在对应的属性名称旁:

extra_attribs = ["rooms_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_importances,attributes),reverse=True)

机器学习实战(基于Scikit-Learn和TensorFlow)(1)_第26张图片

有了这些信息,你可以尝试删除一些不太有用的特征(例如,本例中只有一个ocean_proximity是有用的,我们可以尝试删除其他所有的特征)。

然后,还应该查看一下系统产生的具体错误,尝试了解它们是怎么产生的,以及该怎么解决(通过添加额外的特征,或者是删除没有信息的特征,清除异常值,等等)

通过系统测试集评估系统

有了一个表现足够优秀的系统,现在可以用测试集评估最终模型了,只要从测试集中获取预测器和标签,运行full_pipeline来转换数据(调用transform()而不是fit_transform()),然后在测试集上评估最终模型:

final_model = grid_search.best_estimator_
X_test = strat_test_set.drop("median_house_value",axis=1)
y_test = strat_test_set["median_house_value"].copy()
X_test_prepared = full_pipeline.transform(X_test)
final_predictions = final_model.predict(X_test_prepared)
final_mse = mean_squared_error(y_test,final_predictions)
final_rmse = np.sqrt(final_mse)    #evaluates to 48209.6

如果之前进行过大量的超参数调整,这时的评估结果通常会略逊于你之前还用交叉验证时的表现结果(因为通过不断调整,系统在验证数据上终于表现良好,在未知数据集上可能达不到这么好的效果)。在本例中,结果虽然并非如此,但是当这种情况发生时,你一定要忍住继续调整超参数的诱惑,不要试图在努力让测试集的结果也变得好看一些,因为这些改进在泛化到新的数据集时又会变得徒劳。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(机器学习实战(基于Scikit-Learn和TensorFlow)(1))