机器学习-项目

项目地址

https://github.com/pauljoo/labs-ml

一个完整机器学习项目的主要步骤

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

数据来源

  • 流行的开源数据仓库:
    • UC Irvine Machine Learning Repository
    • Kaggle datasets
    • Amazon’s AWS datasets
  • 准入口(提供开源数据列表)
  • 其它列出流行开源数据仓库的网页:
    • Wikipedia’s list of Machine Learning datasets
    • Quora.com question
    • Datasets subreddit

项目概述

利用加州普查数据,建立一个加州房价模型。这个数据包含每个街区组的人口、收入中位数、房价中位数等指标

划定问题

首先,你需要划定问题:监督或非监督,还是强化学习?这是个分类任务、回归任务,还是其它的?要使用批量学习还是线上学习?

  1. 问老板的第一个问题应该是商业目标是什么?
    建立模型可能不是最终目标。公司要如何使用、并从模型受益?
    这非常重要,因为它决定了如何划定问题,要选择什么算法,评估模型性能的指标是什么,要花多少精力进行微调。
    老板告诉你你的模型的输出(一个区的房价中位数) 会传给另一个机器学习系统,也有其它信号会传入后面的系统。
    这一整套系统可以确定某个区进行投资值不值。确定值不值得投资非常重要,它直接影响利润。

  2. 现在的解决方案效果如何

老板通常会给一个参考性能,以及如何解决问题。
老板说,现在街区的房价是靠专家手工估计的,专家队伍收集最新的关于一个区的信息(不包括房价中位数) ,他们使用复杂的规则进行估计。
这种方法费钱费时间,而且估计结果不理想,误差率大概有 15%。

选择性能指标

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

虽然大多数时候 RMSE 是回归任务可靠的性能指标,在有些情况下,你可能需要另外的函数。
例如,假设存在许多异常的街区。此时,你可能需要使用平均绝对误差(Mean Absolute Error,也称作平均绝对偏差) ,

RMSE 和 MAE 都是测量预测值和目标值两个向量距离的方法。

核实假设

最后,最好列出并核对迄今(你或其他人) 作出的假设,这样可以尽早发现严重的问题。

例如,你的系统输出的街区房价,会传入到下游的机器学习系统,我们假设这些价格确实会被当做街区房价使用。
但是如果下游系统实际上将价格转化成了分类(例如,便宜、中等、昂贵) ,然后使用这些分类,而不是使用价格。
这样的话,获得准确的价格就不那么重要了,你只需要得到合适的分类。
问题相应地就变成了一个分类问题,而不是回归任务。你可不想在一个回归系统上工作了数月,最后才发现真相。

获取数据

1
conda install jupyter matplotlib numpy pandas scipy scikit-learn

创建工作空间

1
jupyter notebook --ip=0.0.0.0 --no-browser --allow-root

创建测试集

在这个阶段就分割数据,听起来很奇怪。毕竟,你只是简单快速地查看了数据而已,你需要再仔细调查下数据以决定使用什么算法。

这么想是对的,但是人类的大脑是一个神奇的发现规律的系统,这意味着大脑非常容易发生过拟合:

如果你查看了测试集,就会不经意地按照测试集中的规律来选择某个特定的机器学习模型。
再当你使用测试集来评估误差率时,就会导致评估过于乐观,而实际部署的系统表现就会差。这称为数据透视偏差。

  1. 纯随机的取样方法
    当你的数据集很大时(尤其是和属性数相比) ,这通常可行;但如果数据集不大,就会有采样偏差的风险。
  2. 分层采样
    将人群分成均匀的子分组,称为分层,从每个分层去取合适数量的实例,以保证测试集对总人数有代表性。

数据探索和可视化、发现规律

前为止,你只是快速查看了数据,对要处理的数据有了整体了解。现在的目标是更深的探索数据。
首先,保证你将测试集放在了一旁,只是研究训练集。

地理数据可视化

通常来讲,人类的大脑非常善于发现图片中的规律,但是需要调整可视化参数使规律显现出来。

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

查找关联

当数据集并不是非常大,可以很容易地使用 corr() 方法计算出每对属性间的标准相关系数(standard correlation coefficient,也称作皮尔逊相关系数)

相关系数的范围是 -1 到 1。

  • 当接近 1 时,意味强正相关;例如,当收入中位数增加时,房价中位数也会增加。
  • 当相关系数接近 -1 时,意味强负相关;
  • 相关系数接近 0,意味没有线性相关性。

警告:相关系数只测量线性关系(如果 x 上升, y 则上升或下降)。
相关系数可能会完全忽略非线性关系(例如,如果 x 接近 0,则 y 值会变高)。

1
2
corr_matrix = housing.corr()
corr_matrix["median_house_value"].sort_values(ascending=False)

scatter_matrix

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

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

最有希望用来预测房价中位数的属性是收入中位数,因此将这张图放大

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

你发现了一些数据的巧合,需要在给算法提供数据之前,将其去除。
你还发现了一些属性间有趣的关联,特别是目标属性。
你还注意到一些属性具有长尾分布,因此你可能要将其进行转换(例如,计算其 log 对数)。
当然,不同项目的处理方法各不相同,但大体思路是相似的。

属性组合试验

给算法准备数据之前,你需要做的最后一件事是尝试多种属性组合。
例如,如果你不知道某个街区有多少户,该街区的总房间数就没什么用。
你真正需要的是每户有几个房间。
相似的,总卧室数也不重要:你可能需要将其与房间数进行比较。
每户的人口数也是一个有趣的属性组合。

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

看起来不错!与总房间数或卧室数相比,新的 bedrooms_per_room 属性与房价中位数的关联更强。
显然,卧室数/总房间数的比例越低,房价就越高。
每户的房间数也比街区的总房间数的更有信息,很明显,房屋越大,房价就越高。

为机器学习算法准备数据

数据清洗

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

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

处理文本和类别属性

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

自定义转换器

尽管 Scikit-Learn 提供了许多有用的转换器,你还是需要自己动手写转换器执行任务,比如自定义的清理操作,或属性组合。
你需要让自制的转换器与 Scikit-Learn 组件(比如流水线) 无缝衔接工作,因为 Scikit-Learn 是依赖依赖类型的(而不是继承),
你所需要做的是创建一个类并执行三个方法: fit() (返回 self ) , transform() ,和 fit_transform() 。

特征缩放

数据要做的最重要的转换之一是特征缩放。除了个别情况,当输入的数值属性量度不同时,机器学习算法的性能都不会好。
这个规律也适用于房产数据:总房间数分布范围是 6 到39320,而收入中位数只分布在 0 到 15。注意通常情况下我们不需要对目标值进行缩放。
有两种常见的方法可以让所有的属性有相同的量度:线性函数归一化(Min-Max scaling)和标准化(standardization) 。

转换流水线

你已经看到,存在许多数据转换步骤,需要按一定的顺序执行。幸运的是,Scikit-Learn 提供了类 Pipeline ,来进行这一系列的转换。

选择并训练模型

在训练集上训练和评估

线性回归模型

  1. 先来训练一个线性回归模型

    1
    2
    3
    from sklearn.linear_model import LinearRegression
    lin_reg = LinearRegression()
    lin_reg.fit(housing_prepared, housing_labels)
  2. 用训练集评估下

    1
    2
    3
    4
    housing_predictions = lin_reg.predict(housing_prepared)
    lin_mse = mean_squared_error(housing_labels, housing_predictions)
    lin_rmse = np.sqrt(lin_mse)
    lin_rmse

DecisionTreeRegressor

  1. 尝试一个更为复杂的模型 DecisionTreeRegressor
    1
    2
    3
    4
    from sklearn.tree import DecisionTreeRegressor
    tree_reg = DecisionTreeRegressor()
    tree_reg.fit(housing_prepared, housing_labels)
    # 68628.413493824875

大多数街区的 median_housing_values 位于 120000到 265000 美元之间,因此预测误差 68628 美元不能让人满意。
这是一个模型欠拟合训练数据的例子。
当这种情况发生时,意味着特征没有提供足够多的信息来做出一个好的预测,或者模型并不强大。

  1. 用训练集评估下
    1
    2
    3
    4
    5
    housing_predictions = tree_reg.predict(housing_prepared)
    tree_mse = mean_squared_error(housing_labels, housing_predictions)
    tree_rmse = np.sqrt(tree_mse)
    tree_rmse
    # 0.0

没有误差?这个模型可能是绝对完美的吗?当然,更大可能性是这个模型严重过拟合数据。
如何确定呢?如前所述,直到你准备运行一个具备足够信心的模型,都不要碰测试集,因此你需要使用训练集的部分数据来做训练,用一部分来做模型验证。

使用交叉验证做更佳的评估

  1. 用函数 train_test_split 来分割训练集
    评估决策树模型的一种方法是用函数 train_test_split 来分割训练集,得到一个更小的训练
    集和一个验证集,然后用更小的训练集来训练模型,用验证集来评估。这需要一定工作量,
    并不难而且也可行。

  2. 用 Scikit-Learn 的交叉验证功能
    另一种更好的方法是使用 Scikit-Learn 的交叉验证功能。下面的代码采用了 K 折交叉验证(K-fold cross-validation) :
    它随机地将训练集分成十个不同的子集,成为“折”,然后训练评估决策树模型 10 次,每次选一个不用的折来做评估,用其它 9 个来做训练。
    结果是一个包含10 个评分的数组。

判断没错:决策树模型过拟合很严重,它的性能比线性回归模型还差。

RandomForestRegressor

1
2
3
4
5
6
from sklearn.ensemble import RandomForestRegressor
forest_reg = RandomForestRegressor()
forest_reg.fit(housing_prepared, housing_labels)
# [...]
forest_rmse
# 22542.396440343684

随机森林看起来很有希望。但是,训练集的评分仍然比验证集的评分低很多。
解决过拟合可以通过简化模型,给模型加限制(即,规整化) ,或用更多的训练数据。
在深入随机森林之前,你应该尝试下机器学习算法的其它类型模型(不同核心的支持向量机,神经网络,等等),
不要在调节超参数上花费太多时间。目标是列出一个可能模型的列表(两到五个) 。

模型微调

假设你现在有了一个列表,列表里有几个有希望的模型。你现在需要对它们进行微调。

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

网格搜索

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

随机搜索

当探索相对较少的组合时,就像前面的例子,网格搜索还可以。但是当超参数的搜索空间很大时,最好使用 RandomizedSearchCV 。

集成方法

另一种微调系统的方法是将表现最好的模型组合起来。
组合(集成) 之后的性能通常要比单独的模型要好(就像随机森林要比单独的决策树要好) ,特别是当单独模型的误差类型不同时。

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

通过分析最佳模型,常常可以获得对问题更深的了解。比如, RandomForestRegressor 可以指出每个属性对于做出准确预测的相对重要性
有了这个信息,你就可以丢弃一些不那么重要的特征(比如,显然只要一个 ocean_proximity 的类型(INLAND) 就够了,所以可以丢弃掉其它的)。
你还应该看一下系统犯的误差,搞清为什么会有些误差,以及如何改正问题(添加更多的特征,或相反,去掉没有什么信息的特征,清洗异常值等等)。

用测试集评估系统

调节完系统之后,你终于有了一个性能足够好的系统。现在就可以用测试集评估最后的模型了。
这个过程没有什么特殊的:从测试集得到预测值和标签,运行 full_pipeline 转换数据(调用 transform() ,而不是 fit_transform() !),再用测试集评估最终模型。

评估结果通常要比交叉验证的效果差一点,如果你之前做过很多超参数微调(因为你的系统在验证集上微调,得到了不错的性能,通常不会在未知的数据集上有同样好的效果)。
这个例子不属于这种情况,但是当发生这种情况时,你一定要忍住不要调节超参数,使测试集的效果变好;这样的提升不能推广到新数据上。
然后就是项目的预上线阶段:你需要展示你的方案(重点说明学到了什么、做了什么、没做什么、做过什么假设、系统的限制是什么,等等),
记录下所有事情,用漂亮的图表和容易记住的表达(比如,“收入中位数是房价最重要的预测量”) 做一次精彩的展示。

启动、监控、维护系统

很好,你被允许启动系统了!你需要为实际生产做好准备,特别是接入输入数据源,并编写测试。

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

  • 评估系统的表现需要对预测值采样并进行评估。
    这通常需要人来分析。分析者可能是领域专家,或者是众包平台(比如 Amazon Mechanical Turk 或 CrowdFlower) 的工人。
    不管采用哪种方法,你都需要将人工评估的流水线植入系统。

  • 你还要评估系统输入数据的质量。
    有时因为低质量的信号(比如失灵的传感器发送随机值,或另一个团队的输出停滞) ,系统的表现会逐渐变差,但可能需要一段时间,系统的表现才能下降到一定程度,触发警报。
    如果监测了系统的输入,你就可能尽量早的发现问题。对于线上学习系统,监测输入数据是非常重要的。

  • 最后,你可能想定期用新数据训练模型。
    你应该尽可能自动化这个过程。如果不这么做,非常有可能你需要每隔至少六个月更新模型,系统的表现就会产生严重波动。
    如果你的系统是一个线上学习系统,你需要定期保存系统状态快照,好能方便地回滚到之前的工作状态。