第二章 端对端的机器学习项目 Part I

这篇文章是本人学习 《Hands-On-Machine-Learning-with-Scikit-Learn-and-TensorFlow》的读书笔记第二篇。整理出来是希望在巩固自己的学习效果的同时,希望能够帮助到同样想学习的人。本人也是小白,可能很多地方理解和翻译不是很到位,希望大家多多谅解和提意见。

这一章将会在一个实际的地产项目中完成一个完整的机器学习项目,其中包括以下步骤:

  • 纵观整个项目
  • 收集数据
  • 通过可视化发现数据规律
  • 为机器学习算法准备数据
  • 选择一个模型并开始训练
  • 微调模型
  • 展示结果
  • 上线、监控、维护系统

1. 纵观项目

建立一个模型预测加利福利亚的房价,给定的数据中包括人口、收入中位数、房价中位数等特征。我们的模型应该通过从这些数据中学习,在给定其他特征的情况下能够预测出任一区的房价中位数。

提出问题的框架

首先我们知道这是一个监督学习的问题,因为我们的数据是有标签的。另外这也是一个回归问题,因为我们需要的是预测一个数值。更具体地,这是一个多元回归的问题,因为系统需要使用多个特征去做预测。最后因为我们面对的不是连续的数据流,不需要经常改变系统适应数据的变化。同时数据量也足够小,可以一次读入内存,所以批量学习就足够了。

选择性能度量值

对于回归问题,常见的性能度量指标是 均方根误差(Root Mean Square Error,RMSE)。它测量的是系统在做预测时错误的标准差。
Equation 2-1. Root Mean Square Error (RMSE)
\operatorname{RMSE}(\mathbf{X}, h)=\sqrt{\frac{1}{m} \sum_{i=1}^{m}\left(h\left(\mathbf{x}^{(i)}\right)-y^{(i)}\right)^{2}}

本书中会用到的一些符号:

  • m 表示的是要计算 RMSE的数据集的大小;
  • \mathbf{x}^{(i)}代表数据集中第i个例子的所有特征值组成的向量;
  • y^{(i)} 代表数据集中第i个例子的标签;
  • \mathbf{X}是一个矩阵,由数据集中所有数据的全部特征组成,每一行代表一个数据,i^{t h} 等于\mathbf{x}^{(i)}的转置;
  • h是系统的预测函数,也称为假设。当给定一个数据的特征矩阵\mathbf{x}^{(i)},它给出该数据一个预测值 \hat{y}^{(i)}=h\left(\mathbf{x}^{(i)}\right)
  • \operatorname{RMSE}(\mathbf{X}, h) 是使用假设h在数据集上计算的损失函数。

当数据中存在离群数据时,我们可以考虑平均绝对误差。
Equation 2-2. Mean Absolute Error
\operatorname{MAE}(\mathbf{X}, h)=\frac{1}{m} \sum_{i=1}^{m}\left|h\left(\mathbf{x}^{(i)}\right)-y^{(i)}\right|
RMSEMAE 都是计算两个向量距离的方法:预测的向量和真实的向量。

  • 计算 RMSE 相当于计算的是欧几里得范数,也称为\ell_{2}范数,记为\|\cdot\|_{2}
  • 计算 MAE 相当于计算的是 \ell_{1}范数,记为\|\cdot\|_{1}。这也被称为曼哈顿距离,因为它计算的是当你只能按照正交街区的方法行走时城市中两个点的距离。
  • 包含 n 个元素的向量 v\ell_{k}范数定义为 \|\mathbf{v}\|_{k}=\left(\left|v_{0}\right|^{k}+\left|v_{1}\right|^{k}+\cdots+\left|v_{n}\right|^{k}\right)^{1/k}
  • 范数的索引越高的话,它越侧重于数值大的数而忽略数值小的数。这也是为什么RMSEMAE 对异常值更敏感。但是当异常值是指数稀少时(像一个钟型曲线),RMSE 表现的更好。

2. 收集数据

通过程序从网上下载数据并解压到 datasets/housing 文件夹。

import numpy as np
import os
import pandas as pd
import tarfile
from six.moves import urllib

DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml/master/"
HOUSING_PATH = os.path.join("datasets", "housing")
HOUSING_URL = DOWNLOAD_ROOT + "datasets/housing/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_url下载文件到 tgz_path
    housing_tgz = tarfile.open(tgz_path)
    housing_tgz.extractall(path=housing_path) #解压文件
    housing_tgz.close()

fetch_housing_data()

使用 Pandas 来读取数据

def load_housing_data(housing_path=HOUSING_PATH):
    csv_path = os.path.join(housing_path, "housing.csv")
    return pd.read_csv(csv_path)
Figure 1:Top five rows of housing data

使用 info()的方法来观察数据,看看数据的行数,各属性的类别,非Null值的个数。

Figure 2:Housing Info

可以看出数据集中有20640个数据,其中 total_bedrooms 中只有20433个非空值,表示其中还有207个空值,后期处理需要注意这个问题。另外 ocean_proximity 数据类型为 object,通过查看数据知为类别型。通过 value_counts() 方法查看类别数及每一类的数量。
Figure 3:Ocean_proximity的类别

通过 describe() 方法对所有数值型特征做个汇总。
Figure 4:Summary of housing data

另一个观察数据的好方法就是直方图,直方图能够表示出每个数值变量在给定范围的数据个数。
Figure 5:Histogram for each numerical attributes

通过观察直方图,发现以下几个问题:

  • median_income不像是用美元表示的,实际上是被缩放过了,被限定在0.5-15之间。
  • housing_median_age 和 median_house_value 都被覆盖了顶部(大于某个值的数据被该值覆盖)。为了处理这种情况,可以考虑:
    -给这些被覆盖的数据找到合适的标签
    -把这些数据从训练数据中删除
  • 不同的属性有不一样的尺度,后面我们会谈到特征放缩
  • 大部分特征都是重尾的:他们更偏向于中位数的右边而不是左边。我们会尝试进行特征转换将这些数据转换成更像正态分布。

创建测试数据集

创建一个测试集,理论上是很简单的。只需要从数据集中随机地抽出20%的数据即可。

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]
Figure 6:Split train test set

这样创建会遇到一个问题,就是每次运行这个函数得到的结果都不一样。可以在 np.random.permutation() 之前使用 np.random.seed(42)固定随机数的种子。但是当我们的数据集有更新时,这两种方法得到的数据集都会被打乱。可以通过计算每个数据标识符的哈希值,把哈希值的最后一个字节小于51(20%*256)的数据划入测试集。这样即使下次数据集有更新,我们的测试集也不会有变化。

import hashlib

#hash值的最后一个字节小于51的划入测试集
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]

但是我们的数据中没有可作为标识符的列,可以利用行号来创建一个。

housing_with_id = housing.reset_index() # 加入 'index' 列
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自带的函数 train_test_split()

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

考虑到 median_income 对预测房价比较重要,为了避免随机创建测试集带来的样本偏差,可以考虑分层抽样。为此需要对数据做一些整理。

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

根据收入的类别,使用Scikit-Learn’s StratifiedShuffleSplit()来进行分层抽样。

from sklearn.model_selection import StratifiedShuffleSplit
#Provides train/test indices to split data in train/test sets.
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]
Figure 7:income_cat 各类别占比

为了使数据恢复原来的样子,我们需要删除 income_cat 这一列。

for data in (strat_train_set, strat_test_set):
    data.drop(['income_cat'],axis=1,inplace=True)

3. 通过可视化来发现数据中的规律

每个圆圈的半径代表该区域的人口数量,颜色代表价格,我们使用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()
Figure 8:California Housing Price

寻找相关性

Figure 9:房价的相关性分析

分析几个可能和房价存在相关性的特征

from pandas.tools.plotting import scatter_matrix
attributes = ["median_house_value", "median_income", "total_rooms", "housing_median_age"]
scatter_matrix(housing[attributes],figsize=(12,8))
Figure 10:Scatter Matrix

Figure 11:median_income 和 median_house_value相关性分析

从图形中可以看出,这两个变量存在正相关;数据在500,000的时候被明显截顶了,且大概在450,000和380,000的位置也存在一条水平线。在训练的时候可能要注意删除这些数据,防止模型学到这些怪异的特征。

特征组合

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"]
Figure 12:Corr_matrix

bedrooms_per_room 比 total_bedrooms相关性更强。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容