diff --git "a/docs/2.\344\270\200\344\270\252\345\256\214\346\225\264\347\232\204\346\234\272\345\231\250\345\255\246\344\271\240\351\241\271\347\233\256.md" "b/docs/2.\344\270\200\344\270\252\345\256\214\346\225\264\347\232\204\346\234\272\345\231\250\345\255\246\344\271\240\351\241\271\347\233\256.md" index 4062d35319fca6917979a291169fb67bb35f0227..674be2a9814ff610f6cbd3fcba5225d7f5131834 100644 --- "a/docs/2.\344\270\200\344\270\252\345\256\214\346\225\264\347\232\204\346\234\272\345\231\250\345\255\246\344\271\240\351\241\271\347\233\256.md" +++ "b/docs/2.\344\270\200\344\270\252\345\256\214\346\225\264\347\232\204\346\234\272\345\231\250\345\255\246\344\271\240\351\241\271\347\233\256.md" @@ -72,47 +72,52 @@ OK,有了这些信息,你就可以开始设计系统了。首先,你需要 ### 选择性能指标 -下一步是选择性能指标。回归问题的典型指标是均方根误差(RMSE)。均方根误差测量的是系统预测误差的标准差。例如,RMSE 等于 50000,意味着,68% 的系统预测值位于实际值的 \$50000 以内,95% 的预测值位于实际值的 \$100000 以内。公式 2-1 展示了计算 RMSE 的方法。 +下一步是选择性能指标。回归问题的典型指标是均方根误差(RMSE)。均方根误差测量的是系统预测误差的标准差。例如,RMSE 等于 50000,意味着,68% 的系统预测值位于实际值的 \$50000 以内,95% 的预测值位于实际值的 \$100000 以内(一个特征通常都符合高斯分布,即满足 “68-95-99.7”规则:大约68%的值落在`1σ`内,95% 的值落在`2σ`内,99.7%的值落在`3σ`内,这里的`σ`等于50000)。公式 2-1 展示了计算 RMSE 的方法。 ![](https://upload-images.jianshu.io/upload_images/7178691-b78d5d4f64b5a8dc.png) 公式 2-1 均方根误差(RMSE) > 符号的含义 -> +> > 这个方程引入了一些常见的贯穿本书的机器学习符号: -> -> * `m`是测量 RMSE 的数据集中的实例数量。 +> +> * `m`是测量 RMSE 的数据集中的实例数量。 > 例如,如果用一个含有 2000 个街区的验证集求 RMSE,则`m = 2000`。 -> -> * $x^{(i)}$ 是数据集第`i`个实例的所有特征值(不包含标签)的向量,$y^{(i)}$ 是它的标签(这个实例的输出值)。  -> -> 例如,如果数据集中的第一个街区位于经度 –118.29°,纬度 33.91°,有 1416 名居民,收入中位数是 \$38372,房价中位数是 \$156400(不考虑其它特征),则有: -> +> +> * $x^{(i)}$ 是数据集第`i`个实例的所有特征值(不包含标签)的向量,$y^{(i)}$ 是它的标签(这个实例的输出值)。  +> +> 例如,如果数据集中的第一个街区位于经度 –118.29°,纬度 33.91°,有 1416 名居民,收入中位数是 \$38372,房价中位数是 \$156400(忽略掉其它的特征),则有: +> > ![](https://upload-images.jianshu.io/upload_images/7178691-25c88114ac85b76c.png) -> +> > 和, -> +> > ![](https://upload-images.jianshu.io/upload_images/7178691-e54122a46b09f013.png) -> -> * `X`是包含数据集中所有实例的所有特征值(不包含标签)的矩阵。每一行是一个实例,第`i`行是 $x^{(i)}$ 的转置,标记为 $x^{(i)T}$。 -? -> 例如,仍然是前面的第一区,矩阵`X`就是: -> +> +> * `X`是包含数据集中所有实例的所有特征值(不包含标签)的矩阵。每一行是一个实例,第`i`行是 $x^{(i)}$ 的转置,记为 $x^{(i)T}$。 +> +> 例如,仍然是前面提到的第一区,矩阵`X`就是: +> > ![](https://upload-images.jianshu.io/upload_images/7178691-e159cd4395a29dca.png) -> -> * `h`是系统的预测函数,也称为假设(hypothesis)。当系统收到一个实例的特征向量 $x^{(i)}$,就会输出这个实例的一个预测值 $\hat y^{(i)} = h(x^{(i)})$($\hat y$ 读作`y-hat`)。 -> +> +> * `h`是系统的预测函数,也称为假设(hypothesis)。当系统收到一个实例的特征向量 $x^{(i)}$,就会输出这个实例的一个预测值 $\hat y^{(i)} = h(x^{(i)})$($\hat y$ 读作`y-hat`)。 +> > 例如,如果系统预测第一区的房价中位数是 \$158400,则 $\hat y^{(1)} = h(x^{(1)}) = 158400$。预测误差是 $\hat y^{(1)} – y^{(1)} = 2000$。 -> `RMSE(X,h)`是使用假设`h`在样本集上测量的损失函数。 -> -> 我们使用小写斜体表示标量值(例如 $\it m$ 或 $\it{y^{(1)}}$)和函数名(例如 $\it h$),小写粗体表示向量(例如 $\bb{x^{(i)}}$),大写粗体表示矩阵(例如 $\bb X$)。 +> +> * `RMSE(X,h)`是使用假设`h`在样本集上测量的损失函数。 +> +> 我们使用小写斜体表示标量值(例如 $\it m$ 或 $\it{y^{(i)}}$)和函数名(例如 $\it h$),小写粗体表示向量(例如 $\bb{x^{(i)}}$),大写粗体表示矩阵(例如 $\bb{X}$)。 + +虽然大多数时候 RMSE 是回归任务可靠的性能指标,在有些情况下,你可能需要另外的函数。例如,假设存在许多异常的街区。此时,你可能需要使用平均绝对误差(Mean Absolute Error,也称作平均绝对偏差),见公式 2-2: + -虽然大多数时候 RMSE 是回归任务可靠的性能指标,在有些情况下,你可能需要另外的函数。例如,假设存在许多异常的街区。此时,你可能需要使用平均绝对误差(Mean Absolute Error,也称作平均绝对偏差,见公式 2-2): ![](https://upload-images.jianshu.io/upload_images/7178691-dc7dc324c5399c23.png) -公式 平均绝对误差 +公式2-2 平均绝对误差 + + RMSE 和 MAE 都是测量预测值和目标值两个向量距离的方法。有多种测量距离的方法,或范数: @@ -131,13 +136,13 @@ RMSE 和 MAE 都是测量预测值和目标值两个向量距离的方法。有 ### 核实假设 -最后,最好列出并核对迄今(你或其他人)作出的假设。,这样可以尽早发现严重的问题。例如,你的系统输出的街区房价,会传入到下游的机器学习系统,我们假设这些价格确实会被当做街区房价使用。但是如果下游系统实际上将价格转化成了分类(例如,便宜、中等、昂贵),然后使用这些分类,而不是使用价格。这样的话,获得准确的价格就不那么重要了,你只需要得到合适的分类。问题相应地就变成了一个分类问题,而不是回归任务。你可不想在一个回归系统上工作了数月,最后才发现真相。 +最后,最好列出并核对迄今(你或其他人)作出的假设,这样可以尽早发现严重的问题。例如,你的系统输出的街区房价,会传入到下游的机器学习系统,我们假设这些价格确实会被当做街区房价使用。但是如果下游系统实际上将价格转化成了分类(例如,便宜、中等、昂贵),然后使用这些分类,而不是使用价格。这样的话,获得准确的价格就不那么重要了,你只需要得到合适的分类。问题相应地就变成了一个分类问题,而不是回归任务。你可不想在一个回归系统上工作了数月,最后才发现真相。 幸运的是,在与下游系统主管探讨之后,你很确信他们需要的就是实际的价格,而不是分类。很好!整装待发,可以开始写代码了。 ## 获取数据 -开始动手。最后用 Jupyter notebook 完整地敲一遍示例代码。完整的代码位于 。 +开始动手。最后用 Jupyter notebook 完整地敲一遍示例代码。完整的代码位于 。 ### 创建工作空间 @@ -257,7 +262,7 @@ notebook 包含一组代码框。每个代码框可以放入可执行代码或 下面是获取数据的函数: -```py +```python import os import tarfile from six.moves import urllib @@ -281,7 +286,7 @@ def fetch_housing_data(housing_url=HOUSING_URL, housing_path=HOUSING_PATH): 然后使用Pandas加载数据。还是用一个小函数来加载数据: -```py +```python import pandas as pd def load_housing_data(housing_path=HOUSING_PATH): @@ -290,29 +295,29 @@ def load_housing_data(housing_path=HOUSING_PATH): ``` -这个函数会返回一个包含所有数据的 Pandas`DataFrame`对象。 +这个函数会返回一个包含所有数据的 Pandas `DataFrame` 对象。 ### 快速查看数据结构 -使用`DataFrame`的`head()`方法查看该数据集的顶部 5 行(见图 2-5)。 +使用`DataFrame`的`head()`方法查看该数据集的前5行(见图 2-5)。 ![](https://upload-images.jianshu.io/upload_images/7178691-07fc05ab3c9e5802.png) -图 2-5 数据集的顶部五行 +图 2-5 数据集的前五行 -每一行都表示一个街区。共有 10 个属性(截图中可以看到 6 个):经度、维度、房屋年龄中位数、总房间数、卧室数量、人口数、家庭数、收入中位数、房屋价值中位数、离大海距离。 +每一行都表示一个街区。共有 10 个属性(截图中可以看到 6 个):经度、维度、房屋年龄中位数、总房间数、总卧室数、人口数、家庭数、收入中位数、房屋价值中位数、离大海距离。 -`info()`方法可以快速查看数据的描述,包括总行数、每个属性的类型和非空值的数量(见图 2-6)。 +`info()`方法可以快速查看数据的描述,特别是总行数、每个属性的类型和非空值的数量(见图 2-6)。 ![](https://upload-images.jianshu.io/upload_images/7178691-deacf48a4fb98035.png) 图 2-6 房屋信息 -数据集中共有 20640 个实例,按照机器学习的标准这个数据量很小,但是非常适合入门。总房间数只有 20433 个非空值,意味着 207 个街区缺少这个值。后面要对它进行处理。 +数据集中共有 20640 个实例,按照机器学习的标准这个数据量很小,但是非常适合入门。我们注意到总房间数只有 20433 个非空值,这意味着有 207 个街区缺少这个值。我们将在后面对它进行处理。 -所有的属性都是数值的,除了离大海距离这项。它的类型是对象,因此可以包含任意 Python 对象,但是因为是从 CSV 文件加载的,所以必然是文本。当查看顶部的五行时,你可能注意到那一列的值是重复的,意味着它可能是一个类别属性。可以使用`value_counts()`方法查看都有什么类型,每个类都有多少街区: +所有的属性都是数值的,除了离大海距离这项。它的类型是对象,因此可以包含任意 Python 对象,但是因为该项是从 CSV 文件加载的,所以必然是文本类型。在刚才查看数据前五项时,你可能注意到那一列的值是重复的,意味着它可能是一项表示类别的属性。可以使用`value_counts()`方法查看该项中都有哪些类别,每个类别中都包含有多少个街区: -```py +```python >>> housing["ocean_proximity"].value_counts() <1H OCEAN 9136 INLAND 6551 @@ -333,7 +338,7 @@ Name: ocean_proximity, dtype: int64 另一种快速了解数据类型的方法是画出每个数值属性的柱状图。柱状图(的纵轴)展示了特定范围的实例的个数。你还可以一次给一个属性画图,或对完整数据集调用`hist()`方法,后者会画出每个数值属性的柱状图(见图 2-8)。例如,你可以看到略微超过 800 个街区的`median_house_value`值差不多等于 \$500000。 -```py +```python %matplotlib inline # only in a Jupyter notebook import matplotlib.pyplot as plt housing.hist(bins=50, figsize=(20,15)) @@ -349,7 +354,7 @@ plt.show() 注意柱状图中的一些点: -1. 首先,收入中位数貌似不是美元(USD)。与数据采集团队交流之后,你被告知数据是经过缩放调整的,过高收入中位数的会变为 15(实际为 15.0001),过低的会变为 5(实际为 0.4999)。在机器学习中对数据进行预处理很正常,不一定是问题,但你要明白数据是如何计算出来的。 +1. 首先,收入中位数貌似不是美元(USD)。与数据采集团队交流之后,你被告知数据是经过缩放调整的,过高收入中位数的会变为 15(实际为 15.0001),过低的会变为 5(实际为 0.4999)。在机器学习中对数据进行预处理很正常,这不一定是个问题,但你要明白数据是如何计算出来的。 2. 房屋年龄中位数和房屋价值中位数也被设了上限。后者可能是个严重的问题,因为它是你的目标属性(你的标签)。你的机器学习算法可能学习到价格不会超出这个界限。你需要与下游团队核实,这是否会成为问题。如果他们告诉你他们需要明确的预测值,即使超过 \$500000,你则有两个选项: @@ -363,7 +368,7 @@ plt.show() 希望你现在对要处理的数据有一定了解了。 -> 警告:稍等!再你进一步查看数据之前,你需要创建一个测试集,放在一旁,再也不看。 +> 警告:稍等!在你进一步查看数据之前,你需要创建一个测试集,将它放在一旁,千万不要再看它。 ### 创建测试集 @@ -371,7 +376,7 @@ plt.show() 理论上,创建测试集很简单:只要随机挑选一些实例,一般是数据集的 20%,放到一边: -```py +```python import numpy as np def split_train_test(data, test_ratio): @@ -385,7 +390,7 @@ def split_train_test(data, test_ratio): 然后可以像下面这样使用这个函数: -```py +```python >>> train_set, test_set = split_train_test(housing, 0.2) >>> print(len(train_set), "train +", len(test_set), "test") 16512 train + 4128 test @@ -396,9 +401,9 @@ def split_train_test(data, test_ratio): 解决的办法之一是保存第一次运行得到的测试集,并在随后的过程加载。另一种方法是在调用`np.random.permutation()`之前,设置随机数生成器的种子(比如`np.random.seed(42)`),以产生总是相同的洗牌指数(shuffled indices)。 -但是如果获取更新后的数据集,这两个方法都会失效。一个通常的解决办法是使用每个实例的识别码,以判定是否这个实例是否应该放入测试集(假设实例有单一且不变的识别码)。例如,你可以计算出每个实例识别码的哈希值,只保留其最后一个字节,如果值小于等于 51(约为 256 的 20%),就将其放入测试集。这样可以保证在多次运行中,测试集保持不变,即使更新了数据集。新的测试集会包含新实例中的 20%,但不会有之前位于训练集的实例。下面是一种可用的方法: +但是如果数据集更新,这两个方法都会失效。一个通常的解决办法是使用每个实例的ID来判定这个实例是否应该放入测试集(假设每个实例都有唯一并且不变的ID)。例如,你可以计算出每个实例ID的哈希值,只保留其最后一个字节,如果该值小于等于 51(约为 256 的 20%),就将其放入测试集。这样可以保证在多次运行中,测试集保持不变,即使更新了数据集。新的测试集会包含新实例中的 20%,但不会有之前位于训练集的实例。下面是一种可用的方法: -```py +```python import hashlib def test_set_check(identifier, test_ratio, hash): @@ -410,37 +415,37 @@ def split_train_test_by_id(data, test_ratio, id_column, hash=hashlib.md5): return data.loc[~in_test_set], data.loc[in_test_set] ``` -不过,房产数据集没有识别码这一列。最简单的方法是使用行索引作为 ID: +不过,房产数据集没有ID这一列。最简单的方法是使用行索引作为 ID: -```py +```python 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") ``` -如果使用行索引作为唯一识别码,你需要保证新数据放到现有数据的尾部,且没有行被深处。如果做不到,则可以用最稳定的特征来创建唯一识别码。例如,一个区的维度和经度在几百万年之内是不变的,所以可以将两者结合成一个 ID: +如果使用行索引作为唯一识别码,你需要保证新数据都放到现有数据的尾部,且没有行被删除。如果做不到,则可以用最稳定的特征来创建唯一识别码。例如,一个区的维度和经度在几百万年之内是不变的,所以可以将两者结合成一个 ID: -```py +```python 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 提供了一些函数,可以用多种方式将数据集分割成多个子集。最简单的函数是`train_test_split`,它的作用和之前的函数`split_train_test`很像,并带有其它一些功能。首先,它有一个`random_state`参数,可以设定前面讲过的随机生成器种子;第二,你可以将种子传递到多个行数相同的数据集,可以在相同的索引上分割数据集(这个功能非常有用,比如你有另一个`DataFrame`作为标签): +Scikit-Learn 提供了一些函数,可以用多种方式将数据集分割成多个子集。最简单的函数是`train_test_split`,它的作用和之前的函数`split_train_test`很像,并带有其它一些功能。首先,它有一个`random_state`参数,可以设定前面讲过的随机生成器种子;第二,你可以将种子传递给多个行数相同的数据集,可以在相同的索引上分割数据集(这个功能非常有用,比如你的标签值是放在另一个`DataFrame`里的): -```py +```python from sklearn.model_selection import train_test_split train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42) ``` -目前为止,我们采用的都是纯随机的取样方法。当你的数据集很大时(尤其是和属性数相比),这通常可行;但如果数据集不大,就会有采样偏差的风险。当一个调查公司想要对 1000 个人进行调查,它们不是在电话亭里随机选 1000 个人出来。调查公司要保证这 1000 个人对人群整体有代表性。例如,美国人口的 51.3% 是女性,48.7% 是男性。所以在美国,严谨的调查需要保证样本也是这个比例:513 名女性,487 名男性。这称作分层采样(stratified sampling):将人群分成均匀的子分组,称为分层,从每个分层去除合适数量的实例,以保证测试集对总人数有代表性。如果调查公司采用纯随机采样,会有 12% 的概率导致采样偏差:女性人数少于 49%,或多于 54%。不管发生那种情况,调查结果都会严重偏差。 +目前为止,我们采用的都是纯随机的取样方法。当你的数据集很大时(尤其是和属性数相比),这通常可行;但如果数据集不大,就会有采样偏差的风险。当一个调查公司想要对 1000 个人进行调查,它们不是在电话亭里随机选 1000 个人出来。调查公司要保证这 1000 个人对人群整体有代表性。例如,美国人口的 51.3% 是女性,48.7% 是男性。所以在美国,严谨的调查需要保证样本也是这个比例:513 名女性,487 名男性。这称作分层采样(stratified sampling):将人群分成均匀的子分组,称为分层,从每个分层去取合适数量的实例,以保证测试集对总人数有代表性。如果调查公司采用纯随机采样,会有 12% 的概率导致采样偏差:女性人数少于 49%,或多于 54%。不管发生那种情况,调查结果都会严重偏差。 -假设专家告诉你,收入中位数是预测房价中位数非常重要的属性。你可能想要保证测试集可以代表整体数据集中的多种收入分类。因为收入中位数是一个连续的数值属性,你首先需要创建一个收入类别属性。再仔细地看一下收入中位数的柱状图(图 2-9): +假设专家告诉你,收入中位数是预测房价中位数非常重要的属性。你可能想要保证测试集可以代表整体数据集中的多种收入分类。因为收入中位数是一个连续的数值属性,你首先需要创建一个收入类别属性。再仔细地看一下收入中位数的柱状图(图 2-9)(译注:该图是对收入中位数处理过后的图): ![](https://upload-images.jianshu.io/upload_images/7178691-6f5f192c61bd9806.png) 图 2-9 收入分类的柱状图 -大多数的收入中位数的值聚集在 2-5(一万美元),但是一些收入中位数会超过 6。数据集中的每个分层都要有足够的实例位于你的数据中,这点很重要。否则,对分层重要性的评估就会有偏差。这意味着,你不能有过多的分层,且每个分层都要足够大。后面的代码通过将收入中位数除以 1.5(以限制收入分类的数量),创建了一个收入类别属性,用`ceil`对值舍入(以产生离散的分类),然后将所有大于 5的分类归入到分类 5: +大多数的收入中位数的值聚集在 2-5(万美元),但是一些收入中位数会超过 6。数据集中的每个分层都要有足够的实例位于你的数据中,这点很重要。否则,对分层重要性的评估就会有偏差。这意味着,你不能有过多的分层,且每个分层都要足够大。后面的代码通过将收入中位数除以 1.5(以限制收入分类的数量),创建了一个收入类别属性,用`ceil`对值舍入(以产生离散的分类),然后将所有大于 5的分类归入到分类 5: ```python housing["income_cat"] = np.ceil(housing["median_income"] / 1.5) @@ -540,7 +545,7 @@ plt.legend() ### 查找关联 -因为数据集并不是非常大,你可以很容易地使用`corr()`方法计算出每对属性间的标准相关系数(也称作皮尔逊相关系数): +因为数据集并不是非常大,你可以很容易地使用`corr()`方法计算出每对属性间的标准相关系数(standard correlation coefficient,也称作皮尔逊相关系数): ```python corr_matrix = housing.corr() @@ -568,7 +573,7 @@ Name: median_house_value, dtype: float64 图 2-14 不同数据集的标准相关系数(来源:Wikipedia;公共领域图片) -> 警告:相关系数只测量线性关系(如果`x`上升,`y`则上升或下降)。相关系数可能会完全忽略非线性关系(即,如果`x`接近 0,则`y`值会变高)。在前面的计算结果中,底部的许多行的相关系数接近于 0,尽管它们的轴并不独立:这些就是非线性关系的例子。另外,第二行的相关系数等于 1 或 -1;这和斜率没有任何关系。例如,你的身高(单位是英寸)与身高(单位是英尺或纳米)的相关系数就是 1。 +> 警告:相关系数只测量线性关系(如果`x`上升,`y`则上升或下降)。相关系数可能会完全忽略非线性关系(例如,如果`x`接近 0,则`y`值会变高)。在上面图片的最后一行中,他们的相关系数都接近于 0,尽管它们的轴并不独立:这些就是非线性关系的例子。另外,第二行的相关系数等于 1 或 -1;这和斜率没有任何关系。例如,你的身高(单位是英寸)与身高(单位是英尺或纳米)的相关系数就是 1。 另一种检测属性间相关系数的方法是使用 Pandas 的`scatter_matrix`函数,它能画出每个数值属性对每个其它数值属性的图。因为现在共有 11 个数值属性,你可以得到`11 ** 2 = 121`张图,在一页上画不下,所以只关注几个和房价中位数最有可能相关的属性(图 2-15): @@ -645,9 +650,9 @@ Name: median_house_value, dtype: float64 + 在将数据传给算法之前,你可以在实时系统中使用这些函数。 -+ 这可以让你方便地尝试多种数据转换,查看那些转换方法结合起来效果最好。 ++ 这可以让你方便地尝试多种数据转换,查看哪些转换方法结合起来效果最好。 -但是,还是先回到清洗训练集(通过再次复制`strat_train_set`),将预测量和标签分开,因为我们不想对预测量和目标值应用相同的转换(注意`drop()`创建了一份数据的备份,而不影响`strat_train_set`): +但是,还是先回到干净的训练集(通过再次复制`strat_train_set`),将预测量和标签分开,因为我们不想对预测量和目标值应用相同的转换(注意`drop()`创建了一份数据的备份,而不影响`strat_train_set`): ```python housing = strat_train_set.drop("median_house_value", axis=1) @@ -656,7 +661,7 @@ housing_labels = strat_train_set["median_house_value"].copy() ### 数据清洗 -大多机器学习算法不能处理特征丢失,因此先创建一些函数来处理特征丢失的问题。前面,你应该注意到了属`性total_bedroom`s有一些缺失值。有三个解决选项: +大多机器学习算法不能处理缺失的特征,因此先创建一些函数来处理特征缺失的问题。前面,你应该注意到了属性`total_bedrooms`有一些缺失值。有三个解决选项: + 去掉对应的街区;