《Sklearn与TensorFlow机器学习实用指南》学习笔记
第2章 一个完整的机器学习项目
案例项目主要步骤:1、项目概述 2、获取数据 3、发现并可视化数据,发现规律 4、为机器学习算法准备数据 5、选择模型,进行训练 6、微调模型 7、给出解决方案 8、部署、监控、维护系统
使用真实数据
一些可以查找数据的地方:
- 流行的开源数据仓库:
UC Irvine Machine Learning Repository
Kaggle datasets
Amazon’s datasets - 准入口(提供开源数据列表)
http://dataportals.org/
http://opendatamonitor.eu/
http://quandl.com/ - 其它列出流行开源数据仓库的网页:
Wikipedia’s list of Machine Learning datasets
Quora.com question
Datasets subreddit
项目概览
利用加州普查数据,建立一个加州房价模型。这个数据包含每个分区组的人口、收入中位数、房价中位数等指标。
划定问题
管道:一系列的数据处理组件被称为数据管道。管道在机器学习系统中很常见,因为有许多数据要处理和转换。
组件通常是异步运行的。每个组件吸纳进大量数据,进行处理,然后将数据传输到另一个数据容器中,而后管道中的另一个组件收入这个数据,然后输出,这个过程依次进行下去。每个组件都是独立的:组件间的接口只是数据容器。若一个组件失效了,下游的组件使用失效组件最后生产的数据,通常可以正常运行(一段时间)。这样就使整个架构相当健壮。
选择性能指标
回归问题的典型指标是均方根误差(RMSE)。均方根误差测量的是系统预测误差的标准差。
获取数据
下载数据
一般情况下,数据是存储于关系型数据库(或其它常见数据库)中的多个表、文档、文件。要访问数据,首先要有密码和登录权限,并要了解数据结构。
这个项目相对简单:只需下载一个压缩文件,housing.tgz,它包含一个CSV文件housing.csv,含有所有数据。
下面是获取数据的函数:
import os
import tarfile
from six.moves import urllib
DOWNLOAD_ROOT = "http://raw.githubusercontent.com/ageron/handson/"
HOUSING_PATH = "datasets/housing"
HOUSING_URL = DOWNLOAD_ROOT + HOUSING_PATH + "/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_tgz = tarfile.open(tgz_path)
housing_tgz.extractall(path=housing_path)
housing_tgz.close()
现在,当调用fetch_housing_data(),就会在工作空间创建一个datasets/housing目录,下载housing.tgz文件,提取出housing.csv。
然后使用Pandas加载数据。还是用一个小函数来加载数据:
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)
这个函数会返回一个包含所有数据的Pandas DataFrame对象。
快速查看数据结构
使用DataFrame的head()方法查看该数据集的顶部5行。
housing = load_housing_data()
housing.head()
info()方法可以快速查看数据的描述,包括总行数、每个属性的类型和非空值的数量。
housing.info()
所有属性都是数值的,除了大海距离这项。它的类型是对象,因此可以包含任意Python对象,但是因为是从CSV文件加载的,所以必然是文本。当查看顶部的5行时,可能注意到那一列的值是重复的,意味着它可能是一个分类属性。可以使用value_counts()
方法查看都有什么类型,每个类都有多少分区:
housing["ocean_proximity"].value_counts()
describe()
方法展示了数值属性的概括。
housing.describe()
另一种快速了解数据类型的方法是画出每个数值属性的柱状图。柱状图(的纵轴)展示了特定范围的实例的个数。还可以一次给一个属性画图,或对完整数据集调用hist()方法,后者会画出每个数值属性的柱状图。例如,可以看到略微超过800个街区的median_house_value值差不多等于500000美元。
%matplotlib inline
import matplotlib.pyplot as plt
housing.hist(bins=50, figsize=(20,15))
plt.show()
注:hist()方法依赖于Matplotlib,后者依赖于用户指定的图形后端以打印到屏幕上。因此在画图之前,需要指定Matplotlib要使用的后端。最简单的方法是使用Jupyter的魔术命令%matplotlib inline。它会告诉Jupyter设定好Matplotlib,以使用Jupyter自己的后端。绘图就会在notebook中渲染了。注意在Jupyter中调用show()不是必要的,因为代码框执行后Jupyter会自动展示图像。
创建测试集
理论上,创建测试集很简单:只要随机挑选一些实例,一般是数据集的20%,放到一边:
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_indeices[test_set_size:]
return data.iloc[train_indices], data.iloc[test_indices]
然后可以像下面使用这个函数:
train_set, test_set = split_train_test(housing, 0.2)
print(len(train_set), "train +", len(test_set), "test")
16512 train + 4128 test
这个方法可行,但是并不完美:若再次运行程序,就会产生一个不同的测试集。多次运行之后,机器学习算法就会得到整个数据集,这是需要避免的。
解决的办法之一是保存第一次运行得到的测试集,并在随后的过程加载。另一个方法是在调用np.random.permutation()
之前,设置随机数生成器的种子(比如np.random.seed(42)
),以产生总是相同的混合指数(shuffled indices)。
但若获取更新后的数据集,这两个方法都会失败。一个通常的解决方法是使用每个实例的识别码,以判断这个实例是否应该放入测试集(假设实例有单一且不变的识别码)。例如,你可以计算出每个实例ID的哈希值,只保留其最后一个字节,如果该值小于等于 51(约为 256 的 20%),就将其放入测试集。这样可以保证在多次运行中,测试集保持不变,即使更新了数据集。新的测试集会包含新实例中的 20%,但不会有之前位于训练集的实例。下面是一种可用的方法:
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:
housing_with_id = housing.reset_index()
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "index")
Scikit-Learn提供了一些函数,可以用多种方式将数据集分割成多个子集。最简单的函数是train_test_split
,它的作用和之前的函数split_train_test
很像,并带有其它一些功能。首先,它有一个random_state参数,可以设定随机生成器种子;第二,可以将种子传递到多个行数相同的数据集,可以在相同的索引上分割数据集。
大多数的收入中位数的值聚集在2-5(万美元),一些收入中位数会超过6。数据集中的每个分层都要有足够的实例位于数据中。代码将收入中位数除以1.5(以限制收入分类的数量),创建了一个收入分类属性,用ceil对值舍入(以产生离散的分类),然后将所有大于5的分类归于到分类5:
housing["income_cat"] = np.ceil(housing["median_income"] / 1.5)
housing["income_cat"].where(housing["income_cat"] < 5, 5.0, inplace=True)
可以根据收入分类,进行分层采样,使用Scikit-Learn的StratifiedShuffleSplit类:
from sklearn.model_selection import StratifiedShuffleSplit
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42