Sklearn的学习

1. 前言

本文是我在学习《Hands-On Machine Learning with Scikit-Learn & TensorFlow》过程中的记录。

2. 一个完整的机器学习项目

一个完整的机器学习项目包括很多步骤:

  1. 项目概述。
  2. 获取数据。
  3. 发现并可视化数据,发现规律。
  4. 为机器学习算法准备数据。
  5. 选择模型,进行训练。
  6. 微调模型。
  7. 给出解决方案。
  8. 步数、监控、维护系统。
项目概述

描述问题,形式化地将其表述出来。

划定问题

商业目标是什么,我们如何使用这个模型,要付出多少精力?

现在的解决方案效果如何?

划定问题:监督或非监督,还是强化学习?是分类任务、回归任务,还是其他?要使用批量学习还是线上学习?

选择性能指标

回归问题的典型指标是均方根误差RMSE。均方根误差测量的是系统测量误差的标准差。

尽管大多数时候RMSE是鬼鬼任务可靠的性能指标,有些情况可能需要其它函数,比如平均绝对误差MAE。

注意到,范数的指数越高,就越关注大的值而忽略小的值。这就是为什么RMSE比MAE对异常值更敏感。

核实假设

对好列出并核对做出的假设,这样可以尽早发现严重的问题。例如,系统输入的街区房价,会传入下游的机器学习系统。但如果下游系统实际上将价格转化成了分类,然后使用分类而不是价格,那获取准确的价格就不那么重要了。

获取数据
1
2
3
4
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)

可以编写一个函数来加载数据。

快速查看数据结构

DataFrame.head()可以查看数据集的前五行。

info方法可以快速查看数据的描述,特别是总行数、每个属性的类型和非空值的数量。

我们卡伊发现空行和异常的数据类型,并规划对其处理。

describe展示了数值属性的概括。注意,其中的空值会被忽略。

hist方法,可以画出每个数值属性的柱状图。画出来之后发现数据似乎有截取,要正确处理这些数据,有时很重要只能重新收集或者舍弃。有时不重要直接利用。并且,有时候需要对数据进行变换,将其变换为正态分布。

创建数据集

创建数据集要尽早,因为如果我们查看你了测试机,就会不经意按照测试集中的规律来选择某个特定的机器学习模型。会有数据透视偏差。

1
2
3
4
5
6
7
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]

这个方法可行,但是如果再次运行数据,就会产生不同的测试集,多次运行之后就会得到整个数据集,这需要避免。

解决方法有两个:

  1. 保存第一次运行得到的测试集;
  2. 调用np.random.permutations()之前,设置随机数生成器的种子,以产生总是相同的洗牌指数。

如果数据集更新两个方法都会失效。通常的解决方法是使用每个实例的ID来判定这个实例是否应该放入测试集。比如计算每个实例ID的哈希值,只保留最后一个字节,判断是否小于等于51,并将其放入测试集。(20%的比例)

1
2
3
4
5
6
7
import hashlib
def test_set_check(identifier, test_ratio, hash):
return hash(np.int64(identifier)).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]

如果没有ID这一列。最简单的方法是使用行索引作为ID。但是需要避免增加或者减少时更高数据的索引值。

或者,可以用最稳定的特征来创建唯一识别码。

Scikit-Learn提供了一些函数,可以用多种方式将数据集分割成多个子集。最简单的函数是train_test_split

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

假设正在研究收入的情况,大部分收入的中位数聚集在2-5万美元,但是有些很高。如果我们要将收入分层,需要有足够的实例位于数据中。为了创建收入类别属性,我们进行如下操作。

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

数据变换好之后,我们就可以进行分层采样了。使用Scikit-LearnStrtifiedShuffleSplit类。

1
2
3
4
5
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]

检查结果,我们可以用value.counts方法查看收入分类比例。

使用同样的代码可以看到测试集放入分类的比例,应用分层采样和随机采样结果不同,随机采样数据集偏差严重。

数据探索和可视化

刚才只是快速查看了数据,对要处理的数据有了整体了解。现在的目标是更深地探索数据。

这时,如果数据集很大,我们可能需要采样出一个探索集。如果数据集比较下,可以直接创建一个副本。

1
housing  = start_train_set.copy()
地理数据可视化

如果存在地理信息,纬度或者其它信息,可以创建一个散点图来进行数据可视化。

1
housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.1)
1
2
3
4
5
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()

这样可以用预先定义的”jet”的颜色图,表示出房价。

查找关联

corr方法计算出每对属性之间的标准相关系数。

corrmatrix["median_house_value"].sort_values(ascending=False)可以看到每个属性和房价中位数的关联度。

注意:相关系数只测量线性关系。

scatter_matrix是另一种检测属性之间相关系数的方法,他能画出每个数值属性对其它数值属性的图。

假设我们发现收入中位数和房价中位数相关性很高。

image-20201017161139688
image-20201017161139688

这张图说明了几点。首先,两者相关性非常高;可以清晰看到向上的趋势,而且数据点不是非常分散。第二,我们之前看到的最高价,清晰地呈现为一条水平线。还有一些不是很明显的直线:一条位于450000美元的直线,一条位于350000美元的直线,一条在280000美元的线,和一些更靠下的线。可能需要去除相应的街区,以防止算法重复这些巧合。

属性组合试验

假设发现了一些数据的巧合,需要在给算法提供数据之前,将其去除。假设发现了一些属性间有趣的关联,特别是目标属性。还有一些属性具有长尾分布,因此可能要将其进行转换。

尝试属性组合:如果不知道街区有多少户,该街区的总房间数就没用。真正需要的是每户有多少房间。

1
2
3
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"]

这时查看相关矩阵。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> corr_matrix = housing.corr()
>>> corr_matrix["median_house_value"].sort_values(ascending=False)
median_house_value 1.000000
median_income 0.687170
rooms_per_household 0.199343
total_rooms 0.135231
housing_median_age 0.114220
households 0.064702
total_bedrooms 0.047865
population_per_household -0.021984
population -0.026699
longitude -0.047279
latitude -0.142826
bedrooms_per_room -0.260070
Name: median_house_value, dtype: float64

发现新的属性与中位数的关联更强。

这一步的数据探索不比非常晚辈,此处的目的是有一个正确的开始,快速发现规律,以得到一个合理的原型。一旦发现得到一个原型,并运行起来,就可以分析它的输出,进而发现更多的规律,然后再回到数据探索。

为机器学习算法准备数据

为机器学习算法准备数据,不要手工来做,要写一些函数,理由:

  • 函数可以让你在任何数据集上方便地重复数据转换。
  • 能慢慢建立自己的转换函数库,复用。
  • 在将数据传给算法之前,可以在实时系统中使用这些函数。
  • 可以尝试多种数据转换,查看哪些效果最好。
数据清洗

属性有一些缺失值有三个解决选项:

  • 去掉对应的街区;
  • 去掉整个属性;
  • 进行赋值。
1
2
3
4
housing.dropna(subset=['total_bedrooms'])
housing.drop("total_bedrooms", axis=1)
median = housing["total_bedrooms"].median()
housing["total_bedrooms"].fillna(median)

Imputer 是Scikit-Learn用于处理缺失值的类。首先创建一个实例,指定用某属性的中位数来替代该属性所有的缺失值。

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

因为只有数值属性才能算出中位数,所以我们需要创建一个不包括文本属性的数据副本。

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

这时就可以用fit方法将imputer实例拟合到训练数据。

1
2
3
imputer.fit(housing_num)
imputer.statistics_
X = imputer.transform(housing_num)

结果是一个包含转换后特征的普通的NumPy数组。

1
housting_tr = pd.DataFrame(X, columns=housing_num.columns)
补充:Scikit-Learn设计

Scikit-Learn 设计
Scikit-Learn 设计的 API 设计的非常好。它的主要设计原则是:

  • 一致性:所有对象的接口一致且简单:
    • 估计器(estimator)。任何可以基于数据集对一些参数进行估计的对象都被称
      为估计器(比如, imputer 就是个估计器)。估计本身是通过 fit() 方法,只
      需要一个数据集作为参数(对于监督学习算法,需要两个数据集;第二个数据
      集包含标签)。任何其它用来指导估计过程的参数都被当做超参数(比
      如 imputer 的 strategy ),并且超参数要被设置成实例变量(通常通过构造器
      参数设置)。
    • 转换器(transformer)。一些估计器(比如 imputer )也可以转换数据集,这
      些估计器被称为转换器。API也是相当简单:转换是通过 transform() 方法,被
      转换的数据集作为参数。返回的是经过转换的数据集。转换过程依赖学习到的
      参数,比如 imputer 的例子。所有的转换都有一个便捷的方
      法 fit_transform() ,等同于调用 fit() 再 transform() (但有
      时 fit_transform() 经过优化,运行的更快)。
    • 预测器(predictor)。最后,一些估计器可以根据给出的数据集做预测,这些
      估计器称为预测器。例如,上一章的 LinearRegression 模型就是一个预测器:
      它根据一个国家的人均 GDP 预测生活满意度。预测器有一个 predict() 方法,
      可以用新实例的数据集做出相应的预测。预测器还有一个 score() 方法,可以
      根据测试集(和相应的标签,如果是监督学习算法的话)对预测进行衡器。
      可检验。所有估计器的超参数都可以通过实例的public变量直接访问(比
      如, imputer.strategy ),并且所有估计器学习到的参数也可以通过在实例变量名
      后加下划线来访问(比如, imputer.statistics_ )。
  • 类不可扩散。数据集被表示成 NumPy 数组或 SciPy 稀疏矩阵,而不是自制的类。
    超参数只是普通的 Python 字符串或数字。
  • 可组合。尽可能使用现存的模块。例如,用任意的转换器序列加上一个估计器,就
    可以做成一个流水线,后面会看到例子。
  • 合理的默认值。Scikit-Learn 给大多数参数提供了合理的默认值,很容易就能创建一个系统。
处理文本和类别属性

前面丢弃了类别属性ocean_proximity,因为它是一个文本属性,不能计算出中位数。大多数机器学习算法喜欢跟数字打交道,我们要把这些文本标签转换为数字。

LabelEncoder 是为我们提供的转换器。

1
2
3
4
from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()
housing_cat = housing["ocean_proximity"]
housing_cat_encoded = encoder.fit_transform(housing_cat)

这里用LabelEncoder转换的方式是错的,它只能用于转换标签。这里没报错是因为只有一列文本特征值,多个文本特征列时候就会出错。应使用factorize方法进行操作。

这样转换之后我们就可以使用数值数据了。可以查看映射表,编码器是通过属性classses_来学习的。

这样其hi还有问题,ML算法会认为两个邻近的值更相似。要解决这个问题,经常给每个分类创建一个二元属性,这叫做One-Hot Encoding。

OneHotEncoder用于将整数分类值转变为独热向量。注意fit_transform()用于2D数组。

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

注意输出结果是一个SciPy稀疏矩阵,而不是NumPy数组。当类别属性有数千个分类时,这样非常有用。如果矩阵每行只有一个1,其余都是0,使用大量内存存储0非常浪费,我们要调用toarray()方法。

LabelBinarizer 我们可以一部执行两个转换,从文本分类到整数分类,再到独热向量。返回的结果是NumPy,参数sparse_output=True可以得到稀疏矩阵。

上面LabelBinarizer使用也是错误的,应该用CategoricalEncoder类。

自定义转换器

Scikit-Learn可以自己动手写转换器执行任务,自制转换器与Scikit-Learn组件无缝衔接,因为Scikit-Learn时依赖鸭子类型(而不是继承)。需要做的时创建一个类并执行3个方法,fit, transform, fit_transform通过添加TransformerMixin作为基类,可以很容得到最后一个。另外,如果添加BaseEstimator作为基类,就能得到两个额外地方法,两者可以方便地进行超参数自动微调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)

add_bedrooms_per_room,默认值设为True。这个超参数可以让人很方便地发现添加了这个属性是否对机器学习算法有帮助。我们可以为每个不能完全确保地数据准备步骤添加一个超参数,瞬狙准备步骤越自动化,可以自动化地操作组合就越多,越容易发现更好用的组合。

特征缩放

当输入的数值属性量度不同时,机器学习算法的性能都不会好。有两种常见方法可以让所有属性有相同的量度:线性函数归一化和标准化。

线性函数归一化:值被转变、重新缩放,直到范围编程0到1.我们通过减去最小值,再除以最大值与最小值的差值,来进行归一化。Scikit-Learn提供了转换器MinMaxScaler实现这个功能。它有一个超参数feature_range可以改变范围。

标准化:首先减去平均值,然后出异方差,使得到的分布具有单位方差。与归一化相比,标准化不会限定到某个范围,但是受到异常值的影响很小。StanardScaler进行标准化。

注意:所放弃只能向训练集拟合,而不是向完整的数据集。

转换流水线

存在许多数据转换步骤,需要按一定顺序执行。Scikit-Learn提供了类Pipeline进行一系列的变换。

1
2
3
4
5
6
7
8
9
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
num_pipeline = Pipeline([
('imputer', Inputer(strategy="median")),
('attribs_adder', CombinedAttributesAdder()),
('std_scaler', StandardScaler())
])

housing_num_tr = num_pipeline.fit_transform(housing_num)

Pipeline构造器需要一个定义步骤顺序的名字/估计器对的列表。除了最后一个估计器,其余都要是转换器。

当调用流水线的fit方法,就会对所有转换器顺序调用fit_transform方法,将每次的输出作为参数传递给下一次调用,以知道最后一个估计器,它只执行fit方法。

流水线暴露相同的方法作为最终的估计器。在这个例子中,最后的估计器是一个StandardScaler,它是一个转换器,因此这个流水线有一个transform方法,可以顺序对数据做所有转换。

有了对数值的流水线,还需要对分类值应用LabelBinarizer。用类FeatureUnion实现这个功能,给它一列转换器,当调用它的transform方法,每个转换器的transform会被并行执行,等待输出,然后将输出合并起来,并返回结果。

一个完整的处理数值和类别属性的流水线:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from sklearn.pipeline import FeatureUnion
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', LabelBinarizer()),
])
full_pipeline = FeatureUnion(transformer_list=[
("num_pipeline", num_pipeline),
("cat_pipeline", cat_pipeline),
])

可以很简单地运行整个流水线:

1
housing_prepared = full_pipeline.fit_transform(housing)

每个子流水线都以一个选择转换器开始:通过选择对应的属性、丢弃其它的,来转换数据,并将输出DataFrame转变成一个NumPy数组。Scikit-Learn没有工具处理DataFrame,我们需要转换一下。

选择并训练模型

再前面限定了问题、获得了数据、探索了数据、采样了一个测试集、写了自动化的转换流水线来清理和为算法准备数据。

在训练集上训练和评估

训练一个线性回归模型。

1
2
3
4
from sklearn.linear_model import LinearRegression

lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)

我们这就有了一个可用的线性回归模型。

1
2
3
4
some_data = housing.iloc[:5]
some_labels = housing_labels.iloc[:5]
some_data_prepared = full_pipeline.transform(some_data)
lin_reg.predict(some_data_prepared)

行得通,尽管预测并不怎么准确。我们可以用Scikit-Learn的mean_squared_error函数,用全部训练集来计算下这个回归模型的RMSE。

1
2
3
4
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)

这个模型的median_housing_values位于120000-265000,预测误差68628太多了,所以判定这是欠拟合。修复的方法是选择一个更强大的模型,给训练算法提供更好的特征,或去掉模型上的限制。模型灭有正则化,所以我们可以增加特征或者换用更复杂的模型。

决策树:

1
2
3
4
5
6
7
from sklearn.tree import DecissionTreeRegressor
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)
使用交叉验证做更加的评估

评估决策树模型的一种方法是用函数train_test_split来分割训练集,得到一个更小的训练集和一个验证机,然后用更小的训练集来训练模型,用验证集来评估。

另一种更号的方法是使用Scikit-Learn的交叉验证功能。下面代码采用了K折交叉验证:它随机将训练集分成十个不同的子集,成为”折“,然后训练评估决策树模型10次。

1
2
3
from sklearn.model_selection import cross_val_score
scores = cross_val_score(tree_reg, housing_prepared, housing_labels, scoring="neg_mean_squared_error", cv=10)
rmse_scores = np.sqrt(-scores)

Scikit-Learn交叉验证期望的是效用函数而不是损失函数,所以要先取负数。

1
2
3
4
def display_scores(scores):
print("Scores:", scores)
print("Mean:", scores.mean())
print("Standard deviation:", scores.std())

我们发现决策树的准确性比线性模型还高,判断发生了过拟合。

尝试最后一个模型RandomForestRegressor随机森林是通过用特征的随机子集训练许多决策树。在其他多个模型之上建立模型称为集成学习,它是推进ML算法的一种好方法。

1
2
3
from sklearn.ensemble import RandomForestRegressor
forest_reg = RandomForestRegressor()
forest_reg.fit(housing_prepared, housing_labels)

随机森林看上去效果好了一些。但是训练集的评分仍然比验证集的评分低很多。解决过拟合可以通过简化模型,给模型加限制,或用更多的数据训练。

保存模型,以便后续使用。可以用Python的模块pickle,或者用sklearn.externals.joblib,后者序列化大NumPy数组更有效率。

1
2
3
from sklearn.externals import joblib
joblib.dump(my_model, "my_model.pkl")
my_model_loaded = joblib.load("my_modle.pkl")
模型微调
网格搜索

微调的一种方法是手工调整超参数,直到找到好的超参数组合。这样做的话会非常冗长,且没有时间探索多种组合。

应该使用Scikit-Learn的GridSearchCV来做这项搜索工作,需要做的是告诉GridSearchCV要试验有哪些超参数,要试验什么值,GridSearchCV就能用交叉验证实验所有可能超参数值的组合。

1
2
3
4
5
6
7
8
from sklearn.model_selection import GridSearchCV
param_grid = [
{'n_estimators':[3, 10, 30], 'max_features':[2, 4, 6, 8]},
{'bootstrp:[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)

这样,网格搜索一工会搜索8种超参数组合,会训练每个模型五次(五折交叉验证)。训练总工会进行90轮。训练完成后,可以得到参数的最佳组合。

1
grid_search.best_params_

还能直接得到最佳的估计器:

1
grid_research.best_estimator_

注意:如果GridSearchCV是以默认值refit=True开始运行的,则一旦用交叉验证找到了最佳的估计器,就会在整个训练集上重新训练。

也可以评估得分。

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

注意:我们还可以像超参数一样处理数据准备的步骤。例如,网格搜索可以自动判断是否添加一个不确定的特征。它还能用相似地方法来自动找到处理异常值、确实特征、特征选择等任务的最佳方法。

随机搜索

当搜索相对较少的组合时,网格搜索还可以。但是当搜索空间很大,最好使用RandomizedSearchCV。它和GridSearchCV和相似,但是通过选择每个超参数的一个随机值的特定数量的随机组合。优点:

  • 随机搜索1000次,会探索每个超参数的1000个不同的值;
  • 可以很发个遍的设定搜索次数,控制计算量。
集成方法

另一种微调系统的方法是将表现最好的模型组合起来。

分析最佳模型和它们的误差

通过分析最佳模型,往往可以获得对问题更深的了解。比如,RandomForestRegressor可以指出每个属性对于做出准确预测的相对重要性:

1
feature_importances = grid_search.best_estimator_.feature_importances

将重要性分数和属性名放到一起:

1
2
3
4
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)

有了这个信息,我们可以丢弃不重要的特征。

用测试集评估系统

调试完系统之后,可以用测试集评估最后的模型。

从测试集得到预测值和标签,运行full_pipeline转换数据(调用transoform而不是fit_transform),再用测试集评估最终模型。

1
2
3
4
5
6
7
8
9
10
11
final_model = grid_search.best_estimator_

X_test = start_test_set.drop("median_house_value", axi=1)
y_test = start_test_set["median_house_value"].copy()

X_test_prepared = full_pipeline.transform(X_test)

final_predictions = final_model.predict(X_test_prapared)

final_mse = mean_squared_error(y_test, final_predictions)
final_rmse = np.sqrt(final_mse)
启动、监控、维护系统

启动系统之后要为实际生产做好准备,特别是接入输入数据源,并编写测试。

还需要编写监控代码,以固定间隔检测系统的实时表现,当发生下降时触发报警。这对于捕获系统崩溃和性能下降十分重要。做监控很常见,因为模型会随着数据的演化而性能下降。

评估系统的表现需要对预测值采样并评估,这个需要将人工评估的流水线植入系统。

还要评估系统输入数据的质量,比如失灵的传感器等等。

最后,有可能想定期用新数据寻来你模型。应该尽可能那个自动实现这个过程。不过不这么做,可能需要每隔至少六个月更新模型,系统的表现就会产生严重波动。如果系统是一个线上学习系统,需要定期保存快照。