第一部分:机器学习基础
(解决简单问题)
一、机器学习概览
二、一个完整的机器学习项目
三、分类
四、训练模型
五、支持向量机
六、决策树
七、集成学习和随机森林
八、降维
第二部分:神经网络与深度学习
(解决复杂问题)
九、启动并运行 TensorFlow
十、人工神经网络介绍
十一、训练深层神经网络
十二、设备和服务器上的分布式 TensorFlow
十三、卷积神经网络
十四、循环神经网络
十五、自编码器
十六、强化学习
附录
附录 C、SVM 对偶问题
附录 D、自动微分
一、机器学习概览
机器学习概念
广义概念:机器学习是让计算机具有学习的能力,无需进行明确编程。
工程概念:计算机程序利用经验 E 学习任务 T,性能是 P,如果针对任务 T 的性能 P 随着经验 E 不断增长,则称为机器学习。
例如,你的垃圾邮件过滤器就是一个机器学习程序,它可以根据垃圾邮件(比如,用户标记的垃圾邮件)和普通邮件(非垃圾邮件,也称作 ham)学习标记垃圾邮件。用来进行学习的样例称作训练集。每个训练样例称作训练实例(或样本)。在这个例子中,任务 T 就是标记新邮件是否是垃圾邮件,经验E是训练数据,性能P需要定义:例如,可以使用正确分类的比例。这个性能指标称为准确率,通常用在分类任务中。
机器学习特点
使用机器学习方法挖掘大量数据,可以发现并不显著的规律。这称作数据挖掘。
机器学习善于:
- 需要进行大量手工调整或需要拥有长串规则才能解决的问题:机器学习算法通常可以简化代码、提高性能。
- 问题复杂,传统方法难以解决:机器学习善于处理对于传统方法太复杂或是没有已知算法的问题。
- 环境有波动:机器学习算法可以适应新数据,自动适应改变。
- 洞察复杂问题和大量数据:机器学习可以帮助人类进行学习。
机器学习分类
机器学习有多种类型,可以根据如下规则进行分类:
- 是否在人类监督下进行训练(监督、非监督、半监督和强化学习,根据训练时监督的量和类型区分)
- 是否可以动态渐进学习(在线学习 vs 批量学习)
- 它们是否只是通过简单地比较新的数据点和已知的数据点,或者在训练数据中进行模式识别,以建立一个预测模型,就像科学家所做的那样(基于实例学习 vs 基于模型学习)
规则并不仅限于以上的,你可以将他们进行组合。例如,一个先进的垃圾邮件过滤器可以使用神经网络模型动态进行学习,用垃圾邮件和普通邮件进行训练。这就让它成了一个在线、基于模型、监督学习系统。
监督学习
监督学习:用来训练算法的训练数据中包含了答案,称为标签。
一个典型的监督学习任务是分类。垃圾邮件过滤器就是一个很好的例子:用许多带有归类(垃圾邮件或普通邮件)的邮件样本进行训练,过滤器必须还能对新邮件进行分类。
另一个典型任务是预测目标数值,例如给出一些特征(里程数、车龄、品牌等等)称作预测值,来预测一辆汽车的价格。这类任务称作回归。要训练这个系统,你需要给出大量汽车样本,包括它们的预测值和标签(即它们的价格)。
属性:数据类型(例如里程数)
特征:属性+值(例如里程数=15000)
降维:目的是简化数据、但是不能失去大部分信息,这样可以运行的更快,占用的硬盘和内存空间更少,有些情况下性能也更好。做法之一是合并若干相关的特征。例如,汽车的里程数与车龄高度相关,降维算法就会将它们合并成一个,表示汽车的磨损。这叫做特征提取。
下面是一些重要的监督学习算法:
- K近邻算法
- 线性回归
- 逻辑回归
- 支持向量机(SVM)
- 决策树和随机森林
- 神经网络
非监督学习
非监督学习:训练数据没有加上标签,系统在没有老师的条件下进行学习。
下面是一些最重要的非监督学习算法:
聚类:
- K均值
- 层次聚类分析(Hierarchical Cluster Analysis,HCA)
- 期望最大值
可视化和降维:
- 主成分分析(Principal Component Analysis,PCA)
- 核主成分分析
- 局部线性嵌入(Locally-Linear Embedding,LLE)
- t-分布邻域嵌入算法(t-distributed Stochastic Neighbor Embedding,t-SNE)
关联性规则学习:
- Apriori 算法
- Eclat 算法
非监督学习应用场景:
- 博客访客数据
你想运行一个聚类算法,检测相似访客的分组。你不会告诉算法某个访客属于哪一类:它会自己找出关系,无需帮助。例如,算法可能注意到 40% 的访客是喜欢漫画书的男性,通常是晚上访问,20% 是科幻爱好者,他们是在周末访问等等。如果你使用层次聚类分析,它可能还会细分每个分组为更小的组。这可以帮助你为每个分组定位博文。
- 可视化算法
给算法大量复杂的且不加标签的数据,算法输出数据的2D或3D图像。算法会试图保留数据的结构(即尝试保留输入的独立聚类,避免在图像中重叠),这样就可以明白数据是如何组织起来的,也许还能发现隐藏的规律。
- 异常检测(anomaly detection)
例如,检测异常的信用卡转账以防欺诈,检测制造缺陷,或者在训练之前自动从训练数据集去除异常值。异常检测的系统使用正常值训练的,当它碰到一个新实例,它可以判断这个新实例是像正常值还是异常值。
- 关联规则学习
它的目标是挖掘大量数据以发现属性间有趣的关系。例如,假设你拥有一个超市。在销售日志上运行关联规则,可能发现买了烧烤酱和薯片的人也会买牛排。因此,你可以将这些商品放在一起。
半监督学习
半监督学习:一些算法可以处理部分带标签的训练数据,通常是大量不带标签数据加上小部分带标签数据。
一些图片存储服务,比如 Google Photos,是半监督学习的好例子。一旦你上传了所有家庭相片,它就能自动识别相同的人 A 出现在相片 1、5、11 中,另一个人 B 出现在相片 2、5、7 中。这是算法的非监督部分(聚类)。现在系统需要的就是你告诉这两个人是谁。只要给每个人一个标签,算法就可以命名每张照片中的每个人,特别适合搜索照片。
多数半监督学习算法是非监督和监督算法的结合。例如,深度信念网络(deep beliefnetworks)是基于被称为互相叠加的受限玻尔兹曼机(restricted Boltzmann machines,RBM)的非监督组件。RBM 是先用非监督方法进行训练,再用监督学习方法进行整个系统微调。
强化学习
强化学习非常不同。学习系统在这里被称为智能体(agent),可以对环境进行观察,选择和执行动作,获得奖励(负奖励是惩罚)。然后它必须自己学习哪个是最佳方法(称为策略,policy),以得到长久的最大奖励。策略决定了智能体在给定情况下应该采取的行动。
例如,许多机器人运行强化学习算法以学习如何行走。DeepMind 的 AlphaGo 也是强化学习的例子:它在 2016 年三月击败了世界围棋冠军李世石(译者注:2017 年五月,AlphaGo 又击败了世界排名第一的柯洁)。它是通过分析数百万盘棋局学习制胜策略,然后自己和自己下棋。要注意,在比赛中机器学习是关闭的;AlphaGo 只是使用它学会的策略。
批量学习
批量学习:系统不能进行持续学习,必须用所有可用数据进行训练(从头开始训练)。
这通常会占用大量时间和计算资源,所以一般是线下做的。首先是进行训练,然后部署在生产环境且停止学习,它只是使用已经学到的策略。这称为离线学习。
如果你想让一个批量学习系统明白新数据(例如垃圾邮件的新类型),就需要从头训练一个系统的新版本,使用全部数据集(不仅有新数据也有老数据),然后停掉老系统,换上新系统。
用全部数据集进行训练会花费大量时间,所以一般是每 24 小时或每周训练一个新系统。如果系统需要快速适应变化的数据(比如,预测股价变化),就需要一个响应更及时的方案。
在线学习
在线学习:用数据实例持续地进行训练,可以一次一个或一次几个实例(称为小批量)。每个学习步骤都很快且廉价,所以系统可以动态地学习到达的新数据。
在线学习很适合系统接收连续流的数据(比如,股票价格),且需要自动对改变作出调整。如果计算资源有限,在线学习是一个不错的方案:一旦在线学习系统学习了新的数据实例,它就不再需要这些数据了,所以扔掉这些数据(除非你想滚回到之前的一个状态,再次使用数据)。这样可以节省大量的空间。
在线学习算法也可以当机器的内存存不下大量数据集时,用来训练系统(这称作核外学习,out-of-core learning)。算法加载部分的数据,用这些数据进行训练,重复这个过程,直到用所有数据都进行了训练。这个整个过程通常是离线完成的,可以把它想成持续学习。
在线学习系统的一个重要参数是,它们可以多快地适应数据的改变:这被称为学习速率。如果你设定一个高学习速率,系统就可以快速适应新数据,但是也会快速忘记老数据(你可不想让垃圾邮件过滤器只标记最新的垃圾邮件种类)。相反的,如果你设定的学习速率低,系统的惰性就会强:即,它学的更慢,但对新数据中的噪声或没有代表性的数据点结果不那么敏感。
在线学习的挑战之一是,如果坏数据被用来进行训练,系统的性能就会逐渐下滑。如果这是一个部署的系统,用户就会注意到。例如,坏数据可能来自失灵的传感器或机器人,或某人向搜索引擎传入垃圾信息以提高搜索排名。要减小这种风险,你需要密集监测,如果检测到性能下降,要快速关闭(或是滚回到一个之前的状态)。你可能还要监测输入数据,对反常数据做出反应(比如,使用异常检测算法)。
基于实例学习
基于实例学习:系统先用记忆学习案例,然后使用相似度测量推广到新的例子。
垃圾邮件过滤器也要能标记类似垃圾邮件的邮件。这就需要测量两封邮件的相似性。一个(简单的)相似度测量方法是统计两封邮件包含的相同单词的数量。如果一封邮件含有许多垃圾邮件中的词,就会被标记为垃圾邮件。
基于模型学习
基于模型学习:建立这些样本的模型,然后使用这个模型进行预测。
# 使用 Scikit-Learn 训练并运行线性模型,预测塞浦路斯人民生活满意度
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import sklearn
# 加载数据
oecd_bli = pd.read_csv("oecd_bli_2015.csv", thousands=',')
gdp_per_capita = pd.read_csv("gdp_per_capita.csv",thousands=',',delimiter='\t',encoding='latin1', na_values="n/a")
# 准备数据
country_stats = prepare_country_stats(oecd_bli, gdp_per_capita)
X = np.c_[country_stats["GDP per capita"]]
y = np.c_[country_stats["Life satisfaction"]]
# 可视化数据
country_stats.plot(kind='scatter', x="GDP per capita", y='Life satisfaction')
plt.show()
# 选择线性模型
lin_reg_model = sklearn.linear_model.LinearRegression()
# 训练模型
lin_reg_model.fit(X, y)
# 对塞浦路斯进行预测
X_new = [[22587]] # 塞浦路斯的人均GDP
print(lin_reg_model.predict(X_new)) # outputs [[5.96242338]]
# 1、斯洛文尼亚的人均 GDP(20732美元)和塞浦路斯差距很小,OECD 数据上斯洛文尼亚的生活满意度是 5.7,可以预测塞浦路斯的生活满意度也是 5.7
# 2、两个临近的国家,葡萄牙和西班牙的生活满意度分别是 5.1 和 6.5。对这三个值进行平均得到5.77,就和基于模型的预测值很接近。这个简单的算法叫做k近邻回归(这个例子中,k=3)
# clf = sklearn.linear_model.LinearRegression() 改为
# clf = sklearn.neighbors.KNeighborsRegressor(n_neighbors=3)
总结步骤:
- 研究数据
- 选择模型
- 用训练数据进行训练(即,学习算法搜寻模型参数值,使代价函数最小)
- 使用模型对新案例进行预测(这称作推断),但愿这个模型推广效果不差
主要挑战
因为你的主要任务是选择一个学习算法并用一些数据进行训练,会导致错误的两件事就是“错误的算法”和“错误的数据”。
错误的数据:
- 训练数据量不足
不同的机器学习算法,包括非常简单的算法,一旦有了大量数据进行训练,在进行去除语言歧义的测试中几乎有相同的性能。对于复杂问题,数据比算法更重要。
- 没有代表性的训练数据
如果样本太小,就会有样本噪声(即,会有一定概率包含没有代表性的数据),但是即使是非常大的样本也可能没有代表性,如果取样方法错误的话。这叫做样本偏差。
- 低质量数据
如果训练集中的错误、异常值和噪声(错误测量引入的)太多,系统检测出潜在规律的难度就会变大,性能就会降低。
花费时间对训练数据进行清理是十分重要的。事实上,大多数据科学家的一大部分时间是做清洗工作的。
如果一些实例是明显的异常值,最好删掉它们或尝试手工修改错误;如果一些实例缺少特征(比如,你的 5% 的顾客没有说明年龄),你必须决定是否忽略这个属性、忽略这些实例、填入缺失值(比如,年龄中位数),或者训练一个含有这个特征的模型和一个不含有这个特征的模型,等等。
- 不相关的特征
只有在训练数据包含足够相关特征、非相关特征不多的情况下,才能进行学习。机器学习项目成功的关键之一是用好的特征进行训练。
这个过程称作特征工程,包括:
特征选择:选取最有用的特征进行训练
特征提取:组合存在的特征,生成一个更有用的特征(可以使用降维算法)
特征新建:收集新数据创建新特征
错误的算法:
- 过拟合训练数据
模型在训练数据上表现很好,但是推广效果不好(以偏概全)。
过拟合发生在相对于训练数据的量和噪声,模型过于复杂的情况。可能的解决方案有:
简化模型:可以选择一个参数更少的模型(比如使用线性模型,而不是高阶多项式模型)、减少训练数据的属性数、或限制一下模型(让它更简单,降低过拟合的风险,被称作正则化regularization)
收集更多的训练数据
减小训练数据的噪声(比如,修改数据错误和去除异常值)
目标是在完美拟合数据和保持模型简单性上找到平衡,确保算法的推广效果。
- 欠拟合训练数据
当模型过于简单时就会发生欠拟合。
例如,生活满意度的线性模型倾向于欠拟合;现实要比这个模型复杂的多,所以预测很难准确,即使在训练样本上也很难准确。
解决这个问题的选项包括:
选择一个更强大的模型,带有更多参数
用更好的特征训练学习算法(特征工程)
减小对模型的限制(比如,减小正则化超参数)
模型验证
将你的数据分成两个集合:训练集和测试集。
正如它们的名字,用训练集进行训练,用测试集进行测试。对新样本的错误率称作推广错误(或样本外错误),通过模型对测试集的评估,你可以预估这个错误。这个值可以告诉你,你的模型对新样本的性能。
一般使用 80% 的数据进行训练,保留20%用于测试。
如果训练错误率低(即你的模型在训练集上错误不多),但是推广错误率高,意味着模型对训练数据过拟合。
因此,评估一个模型很简单:只使用测试集。现在假设你在两个模型之间犹豫不决(比如一个线性模型和一个多项式模型):如何做决定呢?一种方法是两个都训练,然后比较在测试集上的效果。
现在假设线性模型的效果更好,但是你想做一些正则化以避免过拟合。问题是:如何选择正则化超参数的值?一种选项是用 100 个不同的超参数训练100个不同的模型。假设你发现最佳的超参数的推广错误率最低,比如只有 5%。然后就选用这个模型作为生产环境,但是实际中性能不佳,误差率达到了 15%。发生了什么呢?答案在于,你在测试集上多次测量了推广误差率,调整了模型和超参数,以使模型最适合这个集合。这意味着模型对新数据的性能不会高。
这个问题通常的解决方案是,再保留一个集合,称作验证集合。用训练集和多个超参数训练多个模型,选择在验证集上有最佳性能的模型和超参数。当你对模型满意时,用测试集再做最后一次测试,以得到推广误差率的预估。
为了避免“浪费”过多训练数据在验证集上,通常的办法是使用交叉验证:训练集分成互补的子集,每个模型用不同的子集训练,再用剩下的子集验证。一旦确定模型类型和超参数,最终的模型使用这些超参数和全部的训练集进行训练,用测试集得到推广误差率。
(来自第二章P72)
评估决策树模型的一种方法是用函数train_test_split来分割训练集,得到一个更小的训练集和一个验证集,然后用更小的训练集来训练模型,用验证集来评估。
另一种更好的方法是使用 Scikit-Learn 的交叉验证功能。下面的代码采用了 K 折交叉验证(K-fold cross-validation):它随机地将训练集分成十个不同的子集,称为“折”,然后训练评估决策树模型 10 次,每次选一个不用的折来做评估,用其它 9 个来做训练。结果是一个包含 10 个评分的数组。
模型微调
网格搜索
告诉GridSearchCV要试验有哪些超参数,要试验什么值,GridSearchCV就能用交叉验证试验所有可能超参数值的组合。随机搜索
当超参数的搜索空间很大时,最好使用RandomizedSearchCV,和类GridSearchCV很相似,但它不是尝试所有可能的组合,而是通过选择每个超参数的一个随机值的特定数量的随机组合。集成方法
将表现最好的模型组合起来。组合(集成)之后的性能通常要比单独的模型要好(就像随机森林要比单独的决策树要好),特别是当单独模型的误差类型不同时。分析最佳模型和它们的误差
RandomForestRegressor可以指出每个属性对于做出准确预测的相对重要性。用测试集评估系统
从测试集得到预测值和标签,运行full_pipeline转换数据(调用transform(),而不是fit_transform()!),再用测试集评估最终模型。
二、一个完整的机器学习项目
# Python机器学习:Sklearn 与 TensorFlow 机器学习实用指南
# 参考资料:https://github.com/ageron/handson-ml
# 中文翻译:https://github.com/it-ebooks/hands-on-ml-zh
# 第二章:加州房价模型
import os
import tarfile
import numpy as np
import pandas as pd
import hashlib
import seaborn as sns
import matplotlib.pyplot as plt
from pandas.plotting import scatter_matrix
from six.moves import urllib
import joblib
from sklearn.model_selection import train_test_split
from sklearn.model_selection import StratifiedShuffleSplit
from sklearn.model_selection import cross_val_score
from sklearn.impute import SimpleImputer as Imputer
from sklearn.pipeline import Pipeline
from sklearn.pipeline import FeatureUnion
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import LabelBinarizer
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.base import BaseEstimator, TransformerMixin
# 全局变量大写,函数内变量小写
DOWNLOAD_ROOT = 'https://raw.githubusercontent.com/ageron/handson-ml/master/'
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() # 如果使用with open结构,就不用close
# 加载数据
def load_housing_data(housing_path=HOUSING_PATH):
csv_path = os.path.join(housing_path, 'housing.csv')
return pd.read_csv(csv_path)
# 整体数据情况
def check_housing_data(data):
print(data.head())
print(data.info()) # 可以看到总房间数有207个缺失值,除了离大海距离ocean_proximity是object之外,其余都是数值
print(data['ocean_proximity'].value_counts())
print(data.describe()) # 数值属性的概括,空值不计
# 每一列都作为横坐标生成一个图,将横坐标分成50个柱,也就是50个区间(纵坐标代表区间内的个数)
# 1、收入中位数貌似不是美元,数据经过缩放调整,过高会变成15,过低会变成5
# 2、房屋年龄中位数housing_median_age和房屋价值中位数median_house_value也被设了上限,这可能是很严重的问题,因为后者是研究对象,机器学习算法可能学习到价格不会超出这个界限
# 3、这些属性有不同的量度(特征缩放)
# 4、许多柱状图的尾巴很长,相较于左边,他们在中位数的右边延伸过远,可以在后面尝试变换处理这些属性,使其符合正态分布
housing.hist(bins=50, figsize=(20, 15))
plt.show()
# 创建测试集
def split_train_test(data, test_ratio):
shuffled_indices = np.random.permutation(len(data)) # 对数据长度随机排列,作为index
test_set_size = int(len(data) * test_ratio)
test_indices = shuffled_indices[:test_set_size] # 测试集index
train_indices = shuffled_indices[test_set_size:] # 训练集index
return data.iloc[train_indices], data.iloc[test_indices]
def test_set_check(identifier, test_ratio, hash):
return hash(np.int64(identifier)).digest()[-1] < 256 * test_ratio
# 如果再次运行程序,就会得到一个不一样的数据集,多次运行之后,就可能会得到整个数据集,有以下方法可以避免
# 1、保存第一次运行时得到的数据集,并在随后的过程中加载
# 2、在生成随机index之前,设置随机数生成器的种子(比如np.random.seed(42),以产生总是相同的洗牌指数
# 但是一旦数据集更新,以上两个方法都会失效。所以通常的解决办法是使用每个实例的id来判断这个实例是否应该加入测试集(不是直接指定id)
def split_train_test_by_id(data, test_ratio, id_column='id', hash=hashlib.md5):
data.reset_index() # 不使用行索引作为id:保证新数据放在现有数据的尾部,且没有数据被删除
data['id'] = data['longitude'] * 1000 + data['latitude'] # 使用最稳定的特征来创建id,在这里是经纬度
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] # iloc使用数字,loc使用索引
# 上面的方法都是随机取样方法,这里使用分层取样(分层数不能过大,且每个分层要足够大)
# 收入中位数是预测房价中位数非常重要的属性,应该保证测试集可以代表整体数据集中的多种收入分类
# 因为收入中位数是连续的数值属性,所以要先创建收入类别属性
def split_train_test_by_slice(data, test_ratio):
data['income_cat'] = np.ceil(data['median_income'] / 1.5) # 将收入中位数除以1.5,用ceil对值进行舍入,以产生离散分类
data['income_cat'].where(data['income_cat'] < 5, 5.0, inplace=True) # 将所有大于5的分类归入到分类5
# print(data['income_cat'].value_counts().sort_index()/len(data)) # 收入分类比例
# 按照收入分类进行取样,split返回分组后的数据在原数组中的索引
ss = StratifiedShuffleSplit(n_splits=1, test_size=test_ratio, random_state=42)
for train_index, test_index in ss.split(data, data['income_cat']):
strat_train_set = data.loc[train_index]
strat_test_set = data.loc[test_index]
for set in strat_train_set, strat_test_set:
set.drop('income_cat', axis=1, inplace=True) # 将数据复原
return strat_train_set, strat_test_set
# 地理数据可视化
def view_housing_data(data):
# 房屋分布散点图:将每个点的透明度设置小一点,可以更好的看出点的分布
# 说明房价和位置(比如,靠海)和人口密度联系密切
# 可以使用聚类算法来检测主要的聚集,用一个新的特征值测量聚集中心的距离。
# 尽管北加州海岸区域的房价不是非常高,但离大海距离属性也可能很有用,所以这不是用一个简单的规则就可以定义的问题。
# figure1 = plt.figure()
data.plot(kind='scatter', x='longitude', y='latitude', alpha=0.4,
s=data['population']/100, label='population', # 每个圈的半径表示街区的人口
c=data['median_house_value'], cmap=plt.get_cmap('jet'), colorbar=True) # 颜色代表价格
# 查找关联:因为数据集并不是非常大,可以使用corr方法计算出每对属性间的标准相关系数(也称作皮尔逊相关系数)
plt.figure()
corr_matrix = data.corr()
sns.heatmap(corr_matrix, vmax=1, square=True)
print(corr_matrix['median_house_value'].sort_values(ascending=False))
# 另一种检测属性间相关系数的方法:scatter_matrix方法,可以画出每个数值属性对每个其它数值属性的图,这里选择相关性最大的四个属性
# 如果将每个变量对自己作图,主对角线(左上到右下)都会是直线图,所以展示的是每个属性的柱状图
plt.figure()
attributes = ['median_house_value', 'median_income', 'total_rooms', 'housing_median_age']
scatter_matrix(data[attributes], figsize=(12, 8))
# 最有希望用来预测房价中位数的属性是收入中位数,因此将这张图放大,这张图说明了几点:
# 1、相关性非常高;可以清晰地看到向上的趋势,并且数据点不是非常分散
# 2、我们之前看到的最高价,清晰地呈现为一条位于 500000 美元的水平线。这张图也呈现了一些不是那么明显的直线:
# 一条位于 450000 美元的直线,一条位于 350000 美元的直线,一条在 280000 美元的线,和一些更靠下的线。
# 你可能希望去除对应的街区,以防止算法重复这些巧合。
data.plot(kind='scatter', x='median_income', y='median_house_value', alpha=0.1)
# 在上面几步,你发现了一些数据的巧合,需要在给算法提供数据之前,将其去除。
# 你还发现了一些属性间有趣的关联,特别是目标属性。
# 你还注意到一些属性具有长尾分布,因此你可能要将其进行转换(例如,计算其 log 对数)
# 属性组合试验:尝试多种属性组合
# 如果你不知道某个街区有多少户,该街区的总房间数就没什么用。你真正需要的是每户有几个房间。
data['rooms_per_household'] = data['total_rooms'] / data['households']
# 相似的,总卧室数也不重要:你可能需要将其与房间数进行比较。
data['rooms_per_bedroom'] = data['total_rooms'] / data['total_bedrooms']
# 每户的人口数也是一个有趣的属性组合。
data['population_per_household'] = data['population'] / data['households']
# 再看看相关矩阵:
# 与总房间数或卧室数相比,每卧室房间数与房价中位数的关联性更强,每户的房间数也比街区的总房间数的关联性更强
corr_matrix = data.corr()
print(corr_matrix['median_house_value'].sort_values(ascending=False))
plt.show()
rooms_ix, bedrooms_ix, population_ix, household_ix = 3, 4, 5, 6
class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
def __init__(self, add_bedrooms_per_room=True): # no *args or **kargs
self.add_bedrooms_per_room = add_bedrooms_per_room
def fit(self, X, y=None):
return self # nothing else to do
def transform(self, X, y=None):
rooms_per_household = X[:, rooms_ix] / X[:, household_ix]
population_per_household = X[:, population_ix] / X[:, household_ix]
if self.add_bedrooms_per_room:
bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
return np.c_[X, rooms_per_household, population_per_household, bedrooms_per_room]
else:
return np.c_[X, rooms_per_household, population_per_household]
# 自定义转换器:
# 通过选择对应的属性(数值或分类)、丢弃其它的,来转换数据,并将输出 DataFrame 转变成一个 NumPy 数组
class DataFrameSelector(BaseEstimator, TransformerMixin):
def __init__(self, attribute_names):
self.attribute_names = attribute_names
def fit(self, X, y=None):
return self
def transform(self, X):
return X[self.attribute_names].values
# 数据清洗
# 参考:https://blog.csdn.net/weixin_41571493/article/details/82714759
def trans_housing_data(housing):
# 6.0、将预测值和标签分开
housing = housing.drop('median_house_value', axis=1) # drop和fillna等函数中设置inplace=True在原数据上修改,不设置默认不修改,需要赋值给其他变量
# 6.1、处理缺失值:1、去掉缺失的记录,2、去掉整个属性,3、进行赋值(0、平均值、中位数等)
# housing.dropna(subset=['total_bedrooms']) # 1
# housing.drop('total_bedrooms', axis=1) # 2
# median = housing['total_bedrooms'].median()
# housing['total_bedrooms'].fillna(median, inplace=True) # 3
# 自定义转换器:sklearn是鸭子类型的(而不是继承),需要依次执行fit、transform和fit_transform方法
# sklearn提供Imputer类来处理缺失值
housing_num = housing.drop('ocean_proximity', axis=1) # 只有数值属性才有中位数
imputer = Imputer(strategy='median') # imputer.statistics_属性与housing_num.median().values结果一致
imputer.fit(housing_num) # 将imputer实例拟合到训练数据
x = imputer.transform(housing_num) # 将imputer应用到每个属性,结果为Numpy数组
housing_tr = pd.DataFrame(x, columns=housing_num.columns)
# print(housing_tr.describe()) # 缺失值补充完成,全部列都非空
# 6.2、处理文本和类别属性:ocean_proximity
housing_cat = housing['ocean_proximity']
# 将文本属性转换为数值
encoder = LabelEncoder()
housing_cat_encoded = encoder.fit_transform(housing_cat) # 译注:该转换器只能用来转换标签
# housing_cat_encoded, housing_categories = housing_cat.factorize()
# print(housing_cat_encoded[:20]) # 属性转换为数值完成
# print(encoder.classes_) # 属性映射
# 这种做法的问题是,ML 算法会认为两个临近的值比两个疏远的值要更相似。显然这样不对(比如,分类 0 和 4 比 0 和 1 更相似)
# 要解决这个问题,一个常见的方法是给每个分类创建一个二元属性:独热编码(One-Hot Encoding),只有一个属性会等于 1(热),其余会是 0(冷)
encoder = OneHotEncoder()
housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.reshape(-1, 1)) # reshape行转列,结果是一个稀疏矩阵,只存放非零元素的位置
# print(housing_cat_1hot.toarray()[:10]) # 复原为密集数组
# 将以上两步合并(LabelEncoder + OneHotEncoder)
encoder = LabelBinarizer()
housing_cat_1hot = encoder.fit_transform(housing_cat)
# print(housing_cat_1hot) # 默认返回密集数组,设置sparse_output=True可得到一个稀疏矩阵
# 6.3、特征缩放:让所有的属性有相同的量度
# 方法1:线性函数归一化(Min-Max scaling):值被转变、重新缩放,直到范围变成 0 到 1,转换器MinMaxScaler
# 方法2:标准化(standardization):首先减去平均值(所以标准化值的平均值总是 0),然后除以方差,使得到的分布具有单位方差,转换器StandardScaler
# 许多数据转换步骤,需要按一定的顺序执行,sklearn提供了类Pipeline(转换流水线)来完成
# 调用流水线的fit方法,就会对所有转换器顺序调用fit_transform方法,将每次调用的输出作为参数传递给下一个调用,一直到最后一个估计器,它只执行fit方法
num_attribs = list(housing_num)
cat_attribs = ['ocean_proximity']
# 处理缺失值
num_pipeline = Pipeline([
('selector', DataFrameSelector(num_attribs)),
('imputer', Imputer(strategy='median')),
('attribs_adder', CombinedAttributesAdder()),
('std_scaler', StandardScaler())
])
# housing_num_tr = num_pipeline.fit_transform(housing_num) # 单独运行流水线
# 处理类别属性
cat_pipeline = Pipeline([
('selector', DataFrameSelector(cat_attribs)),
('label_binarizer', LabelBinarizer())
])
full_pipeline = FeatureUnion(transformer_list=[
('num_pipeline', num_pipeline),
('cat_pipeline', cat_pipeline)
])
# housing_prepared = full_pipeline.fit_transform(housing) # 运行整个流水线,会报错,暂时不使用
housing_prepared = housing_tr.copy() # 建立副本,不要修改原来的数据
housing_prepared['ocean_proximity'] = housing_cat_encoded # 合并填充缺失值的结果+类别属性转换为数值的结果
return housing_prepared
# 显示模型评分
def display_scores(scores):
print("Scores:", scores)
print("Mean:", scores.mean())
print("Standard deviation:", scores.std())
# 选择并训练模型
def train_housing_data(housing):
# 7.1、线性回归模型
housing_labels = train_set['median_house_value'].copy()
lin_reg = LinearRegression()
lin_reg.fit(housing, housing_labels) # 训练模型
# 部分训练集验证
some_data = housing.iloc[:5]
some_labels = housing_labels[:5]
# some_data = full_pipeline.transform(some_data) # 先将训练集进行处理
print(lin_reg.predict(some_data))
print(some_labels) # 将预测值与实际值进行对比
# 用全部训练集来计算这个回归模型的RMSE
housing_predicions = lin_reg.predict(housing) # 数据拟合
lin_mse = mean_squared_error(housing_labels, housing_predicions) # 计算误差
lin_rmse = np.square(lin_mse)
print(lin_rmse) # 线性回归模型的预测误差非常大
# 7.2、决策树模型:可以发现数据中复杂的非线性关系
tree_reg = DecisionTreeRegressor()
tree_reg.fit(housing, housing_labels) # 训练模型
housing_predicions = tree_reg.predict(housing)
tree_mse = mean_squared_error(housing_labels, housing_predicions)
tree_rmse = np.square(tree_mse)
print(tree_rmse) # 决策树模型,没有误差?
# 7.3、随机森林模型:通过用特征的随机子集训练许多决策树
forest_reg = RandomForestRegressor()
forest_reg.fit(housing, housing_labels) # 训练模型
housing_predicions = forest_reg.predict(housing)
forest_mse = mean_squared_error(housing_labels, housing_predicions)
forest_rmse = np.square(forest_mse)
print(forest_rmse)
# 使用交叉验证做更佳的评估
# 随机地将训练集分成十个不同的子集,成为“折”,然后训练评估决策树模型 10 次,每次选一个不用的折来做评估,用其它 9 个来做训练
# 结果是一个包含10 个评分的数组
lin_scores = cross_val_score(lin_reg, housing, housing_labels, scoring='neg_mean_squared_error', cv=10)
lin_rmse_scores = np.square(-lin_scores)
display_scores(lin_rmse_scores)
tree_scores = cross_val_score(tree_reg, housing, housing_labels, scoring='neg_mean_squared_error', cv=10)
tree_rmse_scores = np.square(-tree_scores)
display_scores(tree_rmse_scores) # 决策树模型过拟合很严重,它的性能比线性回归模型还差
forest_scores = cross_val_score(tree_reg, housing, housing_labels, scoring='neg_mean_squared_error', cv=10)
forest_rmse_scores = np.square(-forest_scores)
display_scores(forest_rmse_scores) # 现在好多了:随机森林看起来很有希望
# 保存试验过的模型
# joblib.dump(my_model, "my_model.pkl")
# my_model_loaded = joblib.load("my_model.pkl")
if __name__ == '__main__':
pd.set_option('display.max_columns', 20) # 最大列数
pd.set_option('display.width', 1000) # 总宽度(字符数)
# 1、获取数据
# fetch_housing_data()
# 2、加载数据
housing = load_housing_data()
# 3、整体数据情况
# check_housing_data(housing)
# 4、拆分数据集(很早就要进行,如果提前查看了测试集,可能会发生过拟合的情况)
# train_set, test_set = split_train_test(housing, 0.2) # 使用index进行采样,每次的测试集都不一样
# train_set, test_set = split_train_test_by_id(housing, 0.2, 'id') # 构造id,使用id进行采样
# train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42) # sklearn分割数据集,random_state生成器种子
train_set, test_set = split_train_test_by_slice(housing, 0.2) # 分层取样
# 对训练集进行研究
housing = train_set.copy()
# 5、地理数据可视化
# view_housing_data(housing)
# 6、数据清洗
housing = trans_housing_data(housing)
# 7、选择并训练模型
train_housing_data(housing)
三、分类
# 下载数据集
from sklearn.datasets import fetch_mldata
mnist = fetch_mldata('MNIST original')
# 数据展示
import matplotlib
import matplotlib.pyplot as plt
some_digit = X[36000]
some_digit_image = some_digit.reshape(28, 28)
plt.imshow(some_digit_image, cmap = matplotlib.cm.binary, interpolation="nearest")
plt.axis("off")
plt.show()
# 创建测试集
import numpy as np
X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]
# 打乱数据集,可以保证交叉验证的每一折都是相似的
shuffle_index = np.random.permutation(60000)
X_train, y_train = X_train[shuffle_index], y_train[shuffle_index]
# 训练一个二分类器,识别数字 5
y_train_5 = (y_train == 5) # True for all 5s, False for all other digits.
y_test_5 = (y_test == 5)
# 挑选一个分类器去训练它,随机梯度下降分类器 SGD 能高效地处理非常大的数据集,一次只处理一条数据,适合在线学习
from sklearn.linear_model import SGDClassifier
sgd_clf = SGDClassifier(random_state=42) # 数据集随机程度
sgd_clf.fit(X_train, y_train_5)
# 预测图片是否为数字 5
sgd_clf.predict([some_digit])
# 预测结果:真正例、假正例、真反例、假反例
# 对分类器进行评估
# 1、K 折交叉验证:将训练集分成 K 折,然后用模型对其中一折进行预测,对其他折进行训练
from sklearn.model_selection import cross_val_score
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy") # 输出精度
# 精度通常来说不是一个好的性能度量指标,此例中只有 10% 的图片是数字 5,所以总是猜测某张图片不是 5也会有 90% 的可能性是对的
# 2、混淆矩阵:输出类别 A 被分类成类别 B 的次数
# 例如:为了知道分类器将 5 误分为 3 的次数,需要查看混淆矩阵的第五行第三列
# 为了计算混淆矩阵,首先你需要有一系列的预测值,这样才能将预测值与真实值做比较
from sklearn.model_selection import cross_val_predict
from sklearn.metrics import confusion_matrix
y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3) # 基于每一个测试折做出一个预测值
confusion_matrix(y_train_5, y_train_pred) # 得到一个混淆矩阵
# 3、准确率/召回率
# 准确率=真正例/(真正例+假正例)
# 准确率(查准):当机器声称事件发生时,事件真正发生的比例(虚警=0,准确率=1)
# 召回率=真正例/(真正例+假反例)
# 召回率(查全):当事件真正发生时,机器成功预测它发生的比例(漏报=0,召回率=1)
# 医院检测疾病的仪器一般都把灵敏度阈值a设的很低:宁愿多虚警也要少漏报
# 死刑复核一般都把a值设的很高:宁愿多漏报也要少虚警
# F值 = 正确率 * 召回率 * 2 / (正确率 + 召回率),是准确率和召回率的调和平均。
# 普通的平均值平等地看待所有的值,而调和平均会给小的值更大的权重。
from sklearn.metrics import precision_score, recall_score, f1_score
precision_score(y_train_5, y_pred) # == 4344 / (4344 + 1307)
recall_score(y_train_5, y_train_pred) # == 4344 / (4344 + 1077)
f1_score(y_train_5, y_pred)
p86
四、训练模型
五、支持向量机
六、决策树
决策树:一种多功能机器学习算法,既可以执行分类任务也可以执行回归任务,甚至包括多输出(multioutput)任务,是一种功能很强大的算法,可以对很复杂的数据集进行拟合。
决策树也是随机森林的基本组成部分,而随机森林是当今最强大的机器学习算法之一。
决策树的训练和可视化
# 在鸢尾花数据集上进行一个决策树分类器的训练
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
iris = load_iris()
X = iris.data[:, 2:] # petal length and width
y = iris.target
tree_clf = DecisionTreeClassifier(max_depth=2)
tree_clf.fit(X, y)
七、集成学习和随机森林
决策树与随机森林
原理
随机森林算法参数解释
随机森林算法参数解释及调优