项目地址
https://github.com/pauljoo/labs-ml
一个完整机器学习项目的主要步骤
- 项目概述。
- 获取数据。
- 发现并可视化数据,发现规律。
- 为机器学习算法准备数据。
- 选择模型,进行训练。
- 微调模型。
- 给出解决方案。
- 部署、监控、维护系统。
数据来源
- 流行的开源数据仓库:
- UC Irvine Machine Learning Repository
- Kaggle datasets
- Amazon’s AWS datasets
- 准入口(提供开源数据列表)
- 其它列出流行开源数据仓库的网页:
- Wikipedia’s list of Machine Learning datasets
- Quora.com question
- Datasets subreddit
项目概述
利用加州普查数据,建立一个加州房价模型。这个数据包含每个街区组的人口、收入中位数、房价中位数等指标
划定问题
首先,你需要划定问题:监督或非监督,还是强化学习?这是个分类任务、回归任务,还是其它的?要使用批量学习还是线上学习?
问老板的第一个问题应该是商业目标是什么?
建立模型可能不是最终目标。公司要如何使用、并从模型受益?
这非常重要,因为它决定了如何划定问题,要选择什么算法,评估模型性能的指标是什么,要花多少精力进行微调。
老板告诉你你的模型的输出(一个区的房价中位数) 会传给另一个机器学习系统,也有其它信号会传入后面的系统。
这一整套系统可以确定某个区进行投资值不值。确定值不值得投资非常重要,它直接影响利润。现在的解决方案效果如何
老板通常会给一个参考性能,以及如何解决问题。
老板说,现在街区的房价是靠专家手工估计的,专家队伍收集最新的关于一个区的信息(不包括房价中位数) ,他们使用复杂的规则进行估计。
这种方法费钱费时间,而且估计结果不理想,误差率大概有 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 | 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 | corr_matrix = housing.corr() |
scatter_matrix
另一种检测属性间相关系数的方法是使用 Pandas 的 scatter_matrix 函数,它能画出每个数值属性对每个其它数值属性的图。
1 | from pandas.tools.plotting import scatter_matrix |
最有希望用来预测房价中位数的属性是收入中位数,因此将这张图放大
1 | housing.plot(kind="scatter", x="median_income",y="median_house_value",alpha=0.1) |
你发现了一些数据的巧合,需要在给算法提供数据之前,将其去除。
你还发现了一些属性间有趣的关联,特别是目标属性。
你还注意到一些属性具有长尾分布,因此你可能要将其进行转换(例如,计算其 log 对数)。
当然,不同项目的处理方法各不相同,但大体思路是相似的。
属性组合试验
给算法准备数据之前,你需要做的最后一件事是尝试多种属性组合。
例如,如果你不知道某个街区有多少户,该街区的总房间数就没什么用。
你真正需要的是每户有几个房间。
相似的,总卧室数也不重要:你可能需要将其与房间数进行比较。
每户的人口数也是一个有趣的属性组合。
1 | housing["rooms_per_household"] = housing["total_rooms"]/housing["households"] |
看起来不错!与总房间数或卧室数相比,新的 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
2
3from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)用训练集评估下
1
2
3
4housing_predictions = lin_reg.predict(housing_prepared)
lin_mse = mean_squared_error(housing_labels, housing_predictions)
lin_rmse = np.sqrt(lin_mse)
lin_rmse
DecisionTreeRegressor
- 尝试一个更为复杂的模型 DecisionTreeRegressor
1
2
3
4from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor()
tree_reg.fit(housing_prepared, housing_labels)
# 68628.413493824875
大多数街区的 median_housing_values 位于 120000到 265000 美元之间,因此预测误差 68628 美元不能让人满意。
这是一个模型欠拟合训练数据的例子。
当这种情况发生时,意味着特征没有提供足够多的信息来做出一个好的预测,或者模型并不强大。
- 用训练集评估下
1
2
3
4
5housing_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
没有误差?这个模型可能是绝对完美的吗?当然,更大可能性是这个模型严重过拟合数据。
如何确定呢?如前所述,直到你准备运行一个具备足够信心的模型,都不要碰测试集,因此你需要使用训练集的部分数据来做训练,用一部分来做模型验证。
使用交叉验证做更佳的评估
用函数 train_test_split 来分割训练集
评估决策树模型的一种方法是用函数 train_test_split 来分割训练集,得到一个更小的训练
集和一个验证集,然后用更小的训练集来训练模型,用验证集来评估。这需要一定工作量,
并不难而且也可行。用 Scikit-Learn 的交叉验证功能
另一种更好的方法是使用 Scikit-Learn 的交叉验证功能。下面的代码采用了 K 折交叉验证(K-fold cross-validation) :
它随机地将训练集分成十个不同的子集,成为“折”,然后训练评估决策树模型 10 次,每次选一个不用的折来做评估,用其它 9 个来做训练。
结果是一个包含10 个评分的数组。
判断没错:决策树模型过拟合很严重,它的性能比线性回归模型还差。
RandomForestRegressor
1 | from sklearn.ensemble import RandomForestRegressor |
随机森林看起来很有希望。但是,训练集的评分仍然比验证集的评分低很多。
解决过拟合可以通过简化模型,给模型加限制(即,规整化) ,或用更多的训练数据。
在深入随机森林之前,你应该尝试下机器学习算法的其它类型模型(不同核心的支持向量机,神经网络,等等),
不要在调节超参数上花费太多时间。目标是列出一个可能模型的列表(两到五个) 。
模型微调
假设你现在有了一个列表,列表里有几个有希望的模型。你现在需要对它们进行微调。
微调的一种方法是手工调整超参数,直到找到一个好的超参数组合。这么做的话会非常冗长,你也可能没有时间探索多种组合。
网格搜索
你应该使用 Scikit-Learn 的 GridSearchCV 来做这项搜索工作。
你所需要做的是告诉 GridSearchCV 要试验有哪些超参数,要试验什么值,GridSearchCV 就能用交叉验证试验所有可能超参数值的组合。
随机搜索
当探索相对较少的组合时,就像前面的例子,网格搜索还可以。但是当超参数的搜索空间很大时,最好使用 RandomizedSearchCV 。
集成方法
另一种微调系统的方法是将表现最好的模型组合起来。
组合(集成) 之后的性能通常要比单独的模型要好(就像随机森林要比单独的决策树要好) ,特别是当单独模型的误差类型不同时。
分析最佳模型和它们的误差
通过分析最佳模型,常常可以获得对问题更深的了解。比如, RandomForestRegressor 可以指出每个属性对于做出准确预测的相对重要性
有了这个信息,你就可以丢弃一些不那么重要的特征(比如,显然只要一个 ocean_proximity 的类型(INLAND) 就够了,所以可以丢弃掉其它的)。
你还应该看一下系统犯的误差,搞清为什么会有些误差,以及如何改正问题(添加更多的特征,或相反,去掉没有什么信息的特征,清洗异常值等等)。
用测试集评估系统
调节完系统之后,你终于有了一个性能足够好的系统。现在就可以用测试集评估最后的模型了。
这个过程没有什么特殊的:从测试集得到预测值和标签,运行 full_pipeline 转换数据(调用 transform() ,而不是 fit_transform() !),再用测试集评估最终模型。
评估结果通常要比交叉验证的效果差一点,如果你之前做过很多超参数微调(因为你的系统在验证集上微调,得到了不错的性能,通常不会在未知的数据集上有同样好的效果)。
这个例子不属于这种情况,但是当发生这种情况时,你一定要忍住不要调节超参数,使测试集的效果变好;这样的提升不能推广到新数据上。
然后就是项目的预上线阶段:你需要展示你的方案(重点说明学到了什么、做了什么、没做什么、做过什么假设、系统的限制是什么,等等),
记录下所有事情,用漂亮的图表和容易记住的表达(比如,“收入中位数是房价最重要的预测量”) 做一次精彩的展示。
启动、监控、维护系统
很好,你被允许启动系统了!你需要为实际生产做好准备,特别是接入输入数据源,并编写测试。
你还需要编写监控代码,以固定间隔检测系统的实时表现,当发生下降时触发报警。这对于捕获突然的系统崩溃和性能下降十分重要。
做监控很常见,是因为模型会随着数据的演化而性能下降,除非模型用新数据定期训练。
评估系统的表现需要对预测值采样并进行评估。
这通常需要人来分析。分析者可能是领域专家,或者是众包平台(比如 Amazon Mechanical Turk 或 CrowdFlower) 的工人。
不管采用哪种方法,你都需要将人工评估的流水线植入系统。你还要评估系统输入数据的质量。
有时因为低质量的信号(比如失灵的传感器发送随机值,或另一个团队的输出停滞) ,系统的表现会逐渐变差,但可能需要一段时间,系统的表现才能下降到一定程度,触发警报。
如果监测了系统的输入,你就可能尽量早的发现问题。对于线上学习系统,监测输入数据是非常重要的。最后,你可能想定期用新数据训练模型。
你应该尽可能自动化这个过程。如果不这么做,非常有可能你需要每隔至少六个月更新模型,系统的表现就会产生严重波动。
如果你的系统是一个线上学习系统,你需要定期保存系统状态快照,好能方便地回滚到之前的工作状态。