《机器学习实战——基于Scikit-Learn和TensorFlow》
这是一本非常好的机器学习和深度学习入门书,既有基本理论讲解,也有实战代码示例。
我将认真阅读此书,并为每一章内容做一个知识笔记。
我会摘录一些原书中的关键语句和代码,若有错误请为我指出。
假设你是一个房地产公司最近新雇佣的数据科学家,需要根据数据来预测任意区域的房价中位数。
习惯良好的数据科学家,要做的第一件事是拿出机器学习项目清单。
第一个问题:业务目标是什么?
=> 输出对一个房价中位数的预测,用于下游机器学习系统。
第二个问题: 当前的解决方案。
=>专家使用复杂的规则来手动估计。
第三个问题:机器学习的框架是什么?
可以参考机器学习的分类,回答 我们的机器学习模型应该属于什么分类。
该问题是监督式学习任务,且预测属于回归任务。不需要对变化数据做出特别调整,数据量不大,只需要批量学习。
回归问题的典型性能衡量指标是均方根误差(RMSE),它测量的是预测过程中,预测错误的 标准偏差 。
R M S E ( X , h ) = 1 / m ∑ i = 1 m h ( x ( i ) − y ( i ) ) 2 RMSE(X,h)=\sqrt{1/m\sum_{i=1}^m{h(x^{(i)}-y^{(i)})^2}} RMSE(X,h)=1/m∑i=1mh(x(i)−y(i))2
其中:
m是测量RMSE所使用的数据集中实例的数量
x ( i ) x^{(i)} x(i)是数据集中,第i个实例的所有特征值的向量(标签特征除外)
y ( i ) y^{(i)} y(i)是标签(也就是我们期待该实例的输出值)
X是数据中所有实例的所有特征值的矩阵(标记特征除外)。每个实例为一行,也就是说第i行等于 x ( i ) x^{(i)} x(i)的转置矩阵,记作 ( x ( i ) ) T (x^{(i)})^{T} (x(i))T。
h是系统的预测函数,也称为一个假设。当给定系统一个实例的特征向量 x ( i ) x^{(i)} x(i),它会输出一个预测值 y ^ ( 1 ) = h ( x ( 1 ) ) = 158400 \hat{y}^{(1)}=h(x^{(1)})=158 400 y^(1)=h(x(1))=158400,该区域的预测误差为 y ^ ( 1 ) − y ( 1 ) = 2000 \hat{y}^{(1)}-y^{(1)}=2000 y^(1)−y(1)=2000。
RMSE(X, h)是使用假设h再示例上测量的成本函数。
当有很多离群区域时,你可以考虑使用平均绝对误差(也称为平均绝对偏差)。
M A E ( X , h ) = 1 / m ∑ i = 1 m ∣ h ( x ( i ) − y ( i ) ) ∣ MAE(X,h)=1/m \sum_{i=1}^m{ \left| h(x^{(i)}-y^{(i)}) \right| } MAE(X,h)=1/m∑i=1m∣∣h(x(i)−y(i))∣∣
它们都是测量两个向量之间的距离。预测向量和目标值向量。
距离或范数的测度方式:
计算平方和的跟(RMSE)对应欧几里得范数,l2范数。
计算绝对值的综合(MAE)对应l1范数,或称曼哈顿距离。
l0给出的是向量的基数(即元素的数量),l无穷则给出了向量中的最大绝对值。
范数指数越高,则越关注大的价值,忽视小的价值。
列举和验证到目前为止(由你或者其他人)做出的假设,这可以才初期检查出严重问题。
首先安装python。
为机器学习的代码和数据集创建一个工作区目录。
安装一些python模块:Jupyter,Pandas,NumPy,Matplotlib,Scikit-Learn。
使用命令 jupyter notebook来启动Jupyter,浏览器通过 http://loaclhost:8888/ 可访问该服务器。
点击New,选择python版本,创建一个Python笔记本。
编写一个小脚本,需要数据时直接运行获取,方便实时更新。
import os
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_tgz = tarfile.open(tgz_path)
housing_tgz.extractall(path=housing_path)
housing_tgz.close()
使用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)
查看数据的属性类型,属性数量。
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)
housing = load_housing_data()
housing.head()
housing.info()
housing["ocean_proximity"].value_counts()
housing.describe()
%matplotlib inline
import matplotlib.pyplot as plt
housing.hist(bins=50, figsize=(20,15))
save_fig("attribute_histogram_plots")
plt.show()
先创建一个测试集,并放置一旁。
因为浏览测试集数据容易跌入某个看似有趣的数据模式,进而选择某个特殊的机器学习模型。然后当再使用测试集对泛化误差进行估算时,估计结果将会过于乐观,该系统启动后的表现将不如预期那般优秀。这叫做 窥探偏误 。
import numpy as np
# For illustration only. Sklearn has train_test_split()
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]
为保证每次运行的数据分割结果一致,可以使用:
# to make this notebook's output identical at every run
np.random.seed(42)
或
import hashlib
def test_set_check(identifier, test_ratio, hash=hashlib.md5):
return hash(np.int64(identifier)).digest()[-1] < 256 * test_ratio
from zlib import crc32
def test_set_check(identifier, test_ratio):
return crc32(np.int64(identifier)) & 0xffffffff < test_ratio * 2**32
def split_train_test_by_id(data, test_ratio, id_column):
ids = data[id_column]
in_test_set = ids.apply(lambda id_: test_set_check(id_, test_ratio))
return data.loc[~in_test_set], data.loc[in_test_set]
分层抽样的测试集中的比例分布与完整数据集中的分布几乎一致,而纯随机抽样的测试集结果则出现了重大偏离。
分层抽样
from sklearn.model_selection import StratifiedShuffleSplit
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]
把测试集放在一边,能探索的只有训练集。
先创建一个副本,这样随便尝试而不会损害训练集。
housing = strat_train_set.copy()
人脑善于从图片中发现模式,但是也需要使用合适的可视化参数。
housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.1)
save_fig("better_visualization_plot")
housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.4,
s=housing["population"]/100, label="population", figsize=(10,7),
c="median_house_value", cmap=plt.get_cmap("jet"), colorbar=True,
sharex=False)
plt.legend()
save_fig("housing_prices_scatterplot")
可以计算出 ***标准相关系数(皮尔逊相关系数)***,相关系数越接近1越正相关,越接近-1越负相关,接近0说明二者相关程度弱。
相关系数
corr_matrix = housing.corr()
注意:
相关系数仅测量线性相关性,可能彻底遗漏非线性相关性。
此过程中,可以清理异常数据、发现有趣联系、转换处理重尾。
此外还可以构想新的可能属性。
本轮探索重要的是加深理解,为下一步做准备。
缺失值处理方法:
缺失值处理
sample_incomplete_rows.dropna(subset=["total_bedrooms"]) # option 1
sample_incomplete_rows.drop("total_bedrooms", axis=1) # option 2
median = housing["total_bedrooms"].median()
sample_incomplete_rows["total_bedrooms"].fillna(median, inplace=True) # option 3
sample_incomplete_rows
机器学习算法易于和数字打交道,可以先将文本其转换为数字。
可使用LabelEncoder或独热编码OneHotEncoder。
需要创建一个类,然后应用三个方法:fit()、transform()、fit_transform()。
最小 - 最大化缩放(归一化)
将值缩放到0-1之间。
将值减去最小值并除以最大值和最小值的差。
MinMaxScaler
标准化
减去平均值,然后除以方差,从而使得结果的分布具备单位方差。
不绑定缩放范围,可能带来问题,但是受异常值影响更小。
流水线使数据转换以正确的顺序来执行。
代码略。
使用不同的模型来评估。
筛选出几个适合的模型,并保存模型参数。
使用交叉验证,K-折交叉验证:将数据随机分割成十个不同的子集,每个子集称为一个折叠(fold),然后对决策树模型进行十次训练和评估——每次挑选一个折叠进行评估,使用另外的九个折叠进行训练。
使用Scikit-Learn的GridSearchCV来帮助找到最好的超参数。
from sklearn.model_selection import GridSearchCV
param_grid = [
# try 12 (3×4) combinations of hyperparameters
{'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
# then try 6 (2×3) combinations with bootstrap set as False
{'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]},
]
forest_reg = RandomForestRegressor(random_state=42)
# train across 5 folds, that's a total of (12+6)*5=90 rounds of training
grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
scoring='neg_mean_squared_error', return_train_score=True)
grid_search.fit(housing_prepared, housing_labels)
当超参数的搜索范围较大时,使用RansomizedSearchCV来代替网格搜索。
将表现最优的模型组合起来。
比如RandomForestRegressor可以指出每个属性的相对重要程度,以此判断并删去一些不重要的 属性。
只需要从测试集中获取预测器和标签,运行full_pipeline来转换数据。
尽管测试集可能表现较差,但是此时不要试图修改超参数,因为在新的数据集上只会泛化得更糟糕。
编写监控代码,定期检查系统的实时性能。
对系统的预测结果进行抽样并评估。
评估输入系统的数据的质量。
使用新鲜的数据定期训练模型,过程尽可能自动化。