1.2 机器学习示例
机器学习的目标是构建一个很难直接实现的函数。要做到这一点,首先需要选择一个模型(model),即一族通用函数,然后需要按照一个流程从这一族函数中选取与目标相匹配的函数,这个流程被称为模型训练(training the model)或模型拟合(fitting the model)。下面是一个简单的示例。
假设我们收集了一些人的身高和体重数据,并绘制在图上。图1-3展示了从职业足球队员名单中提取的一系列数据点。
图1-3 一个简单的示例数据集。图上的每个点代表一名足球运动员的
身高与体重。我们的目标是用这些数据点拟合出一个模型
假设我们想用一个数学函数来描述这些数据点。首先,注意这些数据点大致可以形成一条向图的右上方延伸的直线。在代数中我们学过,形如f(x) = ax + b的函数可以描述一条直线。或许我们可以找到一组合适的a、b值,使得ax + b能很好地匹配这些数据点。这里a、b的值,就是我们需要搞清楚的模型参数(parameter),或者说权重(weight),而这个函数族就是我们的模型。我们可以编写一段Python代码来生成函数族中的任意一个函数:
class GenericLinearFunction:
def __init__(self, a, b):
self.a = a
self.b = b
def evaluate(self, x):
return self.a * x + self.b
那么如何找到正确的a值和b值呢?我们可以使用严格的算法来找到它们,但也可以先在图上用尺子简单地画一条通过图形的直线,然后计算它的公式。图1-4展示了一条直线,这条直线大致遵循这些数据点的趋势。
图1-4 首先注意数据集大致遵循的一个线性趋势,接着找到拟合这些数据的一条直线的公式
只要挑选直线经过的几个点,就可以计算得到直线的公式,并得到类似于f(x) = 4.2x − 137的结果。至此,我们就有一个与数据相匹配的具体函数了。如果这时候我们再测量一个新球员的身高数据,就可以用得到的函数来估计他的体重。这种估计并不完全准确,但应当足够接近真实值,因此是具有实用价值的。下面的代码将GenericLinearFunction
转换为一个特定的函数:
height_to_weight = GenericLinearFunction(a=4.2, b=-137)
height_of_new_person = 73
estimated_weight = height_to_weight.evaluate(height_of_new_person)
只要新得到的数据同样采集自职业足球运动员,就可以得到相当准确的估计。因为这个数据集包含的都是成年男性,年龄范围也相当狭窄,并且每天都练习同一项运动。但如果用这个函数去预测女子足球运动员、奥运举重运动员或者婴儿,就会得到很不准确的结果。生成的函数受限于训练数据。
以上就是机器学习的基本流程。在上面的例子中,训练模型即是所有形如f(x) = ax + b的函数族。实际上,即便这么简单的模型也是非常有用的,并且统计学家们一直在使用它们。如果需要处理更复杂的问题,就必须采用更复杂的模型以及更先进的训练技术了。但核心思想还是一样的:先描述一族可能的函数,再找到函数族中最适合的那个。
Python和机器学习
本书中的所有代码示例都是用Python编写的。为什么选择Python呢?首先,Python是一种通用的程序开发语言,有很强的表达力。此外,在机器学习和数学编程领域,Python本就是最流行的语言之一。这两个优势结合起来,使得Python成为机器学习应用的一个自然选择。
还有一个原因让Python在机器学习领域广受欢迎:它有一大堆强大的数值计算包。本书使用了以下几个包。
- NumPy——这是一个数值计算库,提供了高效的数据结构来表示数值向量和数组,还配备了功能完善的高速数学运算库。NumPy是Python数值计算生态的基石:许多机器学习和统计库都集成了NumPy。
- TensorFlow和Theano——这是两个图计算库(这里的图,指的是由相互连接的节点所组成的网络结构,而不是图表中的图形)。它们可以定义复杂的数学运算序列,然后生成高度优化的实现。
- Keras——这是一个深度学习的高级库。它提供了很多便捷的方法来配置神经网络。Keras后台依赖于TensorFlow或Theano来进行原始计算。
本书代码示例所用的库版本分别是Keras 2.2和TensorFlow 1.8。理论上,只要进行少许修改,这些代码就能在Keras 2.x系列的任何版本中运行。
1.2.1 在软件应用中使用机器学习
在1.2节中,我们讨论了一个纯粹的数学模型。那么如何将机器学习应用于真实的软件应用中呢?
假设有一个照片共享应用,用户已经上传了数百万张附带标签的照片。这时我们想要添加一个新功能:为新照片推荐相关标签。这个功能非常适合使用机器学习。
首先我们需要确定想要学习的函数。假设函数是下面这样的:
def suggest_tags(image_data):
"""Recommend tags for an image.
Input: image_data is a photo in bitmap format
Returns: a ranked list of suggested tags
"""
有了这个函数,其他的功能就相对容易实现了。但如何着手实现suggest_tags
这个函数本身呢?很难找到头绪。而这正是机器学习能够发挥作用的地方。
如果这是一个普通的Python函数,它的输入应当是某种Image
对象,它返回的应当是一个字符串列表。但机器学习算法的输入和输出就没那么灵活了:机器学习通常只能处理向量和矩阵。因此,工作的第一步是用数学的形式来表示这个函数的输入和输出。
如果将输入的照片尺寸转换为固定尺寸(如128像素×128像素),我们就可以把它编码成128行、128列的矩阵了,这时照片的每个像素对应一个浮点数值。那么对于输出该如何处理呢?一种办法是限定识别的标签集合,例如,可以只选择应用里最流行的1000个标签。这样函数的输出就可以设为大小为1000的向量了,而它的每个元素对应一个标签。如果把标签输出值设置为0~1的变化数值,那么函数就可按照这个建议值的顺序生成有序的标签列表了。图1-5展示了这个应用中每个概念与数学结构之间的映射。
图1-5 机器学习算法只能操作向量或矩阵之类的数学结构,而在这个照片应用里,用来存储标签的字符串列表是一种标准的计算机数据结构。本图展示将标签列表编码成数学向量的一种可能方案
我们在上面所做的数据预处理,是所有机器学习系统都不可或缺的一个步骤。在机器学习中,我们通常会加载原始格式的数据,然后执行预处理步骤,创建一系列特征(feature),并作为输入数据发送给机器学习算法。
1.2.2 监督学习
接下来,我们需要一个用来训练模型的算法。在前面的示例中,我们已经拥有数以百万的正确样本,即用户在应用中上传并手动标记过的所有照片。我们可以训练一个函数来尽可能地拟合这些样本数据,并希望这个函数能够聪明地处理新照片。我们把这种技术称为监督学习(supervised learning)。这么命名的原因是我们利用了人工整理的标签数据来监督指导训练过程。
训练完成之后,会得到一个函数,然后集成到应用中发布。每当用户上传新照片时,照片数据会传递给训练好的模型函数,并获得一个结果向量。接着我们就可以把结果向量中的每个值映射回它所代表的标签,然后选择数值最大的标签显示给用户。整个流程如图1-6所示。
图1-6 基于监督学习的机器学习流程
那么如何测试训练得到的模型呢?标准的做法是将原始标签数据提前预留出一部分用于测试。在训练开始之前,我们可以将数据中的一小部分(如10%)留作验证集(validation set)。验证集中的数据不能以任何形式参与训练过程。训练完成后,用得到的模型来处理验证集中的图像,并把模型建议的标签与已知的正确标签进行比较。这样就可以计算出训练模型的准确率(accuracy)了。如果想尝试不同的模型,可以把这个准确率作为一致标准,来衡量哪个模型更好。
在游戏AI中,我们可以从人类游戏的记录中提取带标签的训练数据。在线游戏对机器学习来说是一个巨大的促进:当人们在线玩游戏时,游戏服务器都可以保存一份计算机可识别的记录。我们举几个在游戏中使用监督学习的例子:
- 假设有一个国际象棋游戏的完整棋谱集合,可以用向量或矩阵形式来表示游戏状态,并从这些数据中学习如何预测下一手落子动作;
- 对于给定的棋盘状态,学习如何预测本局的胜负概率。
1.2.3 无监督学习
机器学习还有另一个子领域,称为无监督学习(unsupervised learning)。与监督学习不同,它不用任何标签来指导学习过程。在无监督学习中,算法必须想办法自己从输入数据中识别出模式。无监督学习的学习流程与图1-6所示的监督学习流程的唯一区别就在于它缺少标签,因此它无法像监督学习那样评估模型的预测结果。
异常值检测(outlier detection)问题,即识别不符合数据集总体趋势的数据点的问题,是无监督学习的一个典型案例。在足球运动员数据集中,异常值指的是与队员典型体格不相符的数据。例如,假设有一个身高×宽度的数据点,并且我们已经为模型拟合出一条平均直线,那就可以想出一个算法来计算这个数据点与平均直线之间的距离。如果距离超过了某个阈值,就可以把这个数据点看作异常值了。
在棋盘游戏AI中,一个很自然的问题是如何检查棋子间的相互关联,即检查哪些棋子形成一个组合。我们将在第3章中更详细地解释它对于围棋的意义。这种搜寻关联个体所形成的组合的问题被称为聚类(clustering)或组块(chunking)。图1-7展示了一个国际象棋的例子。
图1-7 用于查找棋子的聚类或组块的无监督学习流程
1.2.4 强化学习
监督学习很强大,但如何找到高质量的训练数据可能会是一个主要问题。假设我们想要设计一个扫地机器人。它有很多传感器,用于检测是否靠近障碍物;它还有电动引擎,可以在地板上飞奔或转向。它需要一个控制系统,负责分析传感器的输入并决定应该如何移动。但这个问题无法用监督学习来解决,因为我们无法得到可以用作训练数据的样本——扫地机器人还没制造出来。
对此,我们可以用强化学习(reinforcement learning)来解决。强化学习是一种反复试错的方法。我们先从一个效率很低、精度不够高的基本控制系统开始,让机器人不断地尝试完成它的任务。在任务执行期间,我们把控制系统遇到的所有输入以及它所做的所有决策都记录下来。任务完成之后,用某种方法来评估控制系统的表现,例如,可以计算它实际覆盖到地板面积的比例,或计算它的耗电程度。这个过程能够提供一小批训练数据,我们用它们来改进控制系统。接着再反复不断地执行这个过程,我们就可以逐步得到更加高效的控制系统了。图1-8展示了训练过程的流程图。
图1-8 在强化学习中,机器人通过反复试错来学习如何与环境进行交互。它通过反复尝试完成任务来获得可供学习的监督信号数据。每经过一个训练周期,都能够得到一点增量改进