第3章 神经网络入门
本章的目的是让你开始用神经网络来解决实际问题。你将进一步巩固在第 2 章第一个示例中学到的知识,还会将学到的知识应用于三个新问题,这三个问题涵盖神经网络最常见的三种使用场景:二分类问题、多分类问题和标量回归问题。
本章将进一步介绍神经网络的核心组件,即层、网络、目标函数和优化器;还会简要介绍Keras,它是贯穿本书的 Python 深度学习库。你还将建立深度学习工作站,安装好 TensorFlow和Keras,并支持GPU。
最后,我们将用三个介绍性示例深入讲解如何使用神经网络解决实际问题,这三个示例分别是:
学完本章,你将能够使用神经网络解决简单的机器问题,比如对向量数据的分类问题和回归问题。然后,你就可以从第 4 章开始建立对机器学习更加具有原则性、理论性的理解。
3.1 神经网络剖析
前面几章介绍过,训练神经网络主要围绕以下四个方面。
你可以将这四者的关系可视化,如图 3-1 所示:多个层链接在一起组成了网络,将输入数据映射为预测值。然后损失函数将这些预测值与目标进行比较,得到损失值,用于衡量网络预测值与预期结果的匹配程度。优化器使用这个损失值来更新网络的权重。
我们来进一步研究层、网络、损失函数和优化器。
3.1.1 层:深度学习的基础组件
我们在第 2 章中介绍过,神经网络的基本数据结构是层。层是一个数据处理模块,将一个或多个输入张量转换为一个或多个输出张量。有些层是无状态的,但大多数的层是有状态的,即层的权重。权重是利用随机梯度下降学到的一个或多个张量,其中包含网络的知识。
不同的张量格式与不同的数据处理类型需要用到不同的层。
例如,
(1)简单的向量数据保存在形状为 (samples, features) 的 2D 张量中,通常用密集连接层[densely connected layer,也叫全连接层(fully connected layer)或密集层(dense layer),对应于 Keras 的 Dense 类]来处理。
(2)序列数据保存在形状为 (samples, timesteps, features) 的 3D 张量中,通常用循环层(recurrent layer,比如 Keras 的 LSTM 层)来处理。
(3)图像数据保存在 4D 张量中,通常用二维卷积层(Keras 的 Conv2D)来处理。
你可以将层看作深度学习的乐高积木,Keras 等框架则将这种比喻具体化。在 Keras 中,构建深度学习模型就是将相互兼容的多个层拼接在一起,以建立有用的数据变换流程。这里层兼容性(layer compatibility)具体指的是每一层只接受特定形状的输入张量,并返回特定形状的输出张量。看看下面这个例子。
from keras import layers
layer = layers.Dense(32, input_shape=(784,))
我们创建了一个层,只接受第一个维度大小为 784 的 2D 张量(第 0 轴是批量维度,其大小没有指定,因此可以任意取值)作为输入。这个层将返回一个张量,第一个维度的大小变成了 32。
因此,这个层后面只能连接一个接受 32 维向量作为输入的层。使用 Keras 时,你无须担心兼容性,因为向模型中添加的层都会自动匹配输入层的形状,例如下面这段代码。
from keras import models
from keras import layers
model = models.Sequential()
model.add(layers.Dense(32, input_shape=(784,)))
model.add(layers.Dense(32))
其中第二层没有输入形状(input_shape)的参数,相反,它可以自动推导出输入形状等于上一层的输出形状。
3.1.2 模型:层构成的网络
深度学习模型是层构成的有向无环图。最常见的例子就是层的线性堆叠,将单一输入映射为单一输出。但随着深入学习,你会接触到更多类型的网络拓扑结构。
一些常见的网络拓扑结构如下。
网络的拓扑结构定义了一个假设空间(hypothesis space)。你可能还记得第 1 章里机器学习的定义:“在预先定义好的可能性空间中,利用反馈信号的指引来寻找输入数据的有用表示。”
选定了网络拓扑结构,意味着将可能性空间(假设空间)限定为一系列特定的张量运算,将输入数据映射为输出数据。然后,你需要为这些张量运算的权重张量找到一组合适的值。
选择正确的网络架构更像是一门艺术而不是科学。虽然有一些最佳实践和原则,但只有动手实践才能让你成为合格的神经网络架构师。后面几章将教你构建神经网络的详细原则,也会帮你建立直觉,明白对于特定问题哪些架构有用、哪些架构无用。
3.1.3 损失函数与优化器:配置学习过程的关键
一旦确定了网络架构,你还需要选择以下两个参数。
损失函数(目标函数)——在训练过程中需要将其最小化。它能够衡量当前任务是否已成功完成。
优化器——决定如何基于损失函数对网络进行更新。它执行的是随机梯度下降(SGD)的某个变体。
具有多个输出的神经网络可能具有多个损失函数(每个输出对应一个损失函数)。但是,梯度下降过程必须基于单个标量损失值。因此,对于具有多个损失函数的网络,需要将所有损失函数取平均,变为一个标量值。
选择正确的目标函数对解决问题是非常重要的。网络的目的是使损失尽可能最小化,因此,如果目标函数与成功完成当前任务不完全相关,那么网络最终得到的结果可能会不符合你的预期。想象一下,利用 SGD 训练一个愚蠢而又无所不能的人工智能,给它一个蹩脚的目标函数:“将所有活着的人的平均幸福感最大化”。为了简化自己的工作,这个人工智能可能会选择杀死绝大多数人类,只留几个人并专注于这几个人的幸福——因为平均幸福感并不受人数的影响。这可能并不是你想要的结果!请记住,你构建的所有神经网络在降低损失函数时和上述的人工智能一样无情。因此,一定要明智地选择目标函数,否则你将会遇到意想不到的副作用。
幸运的是,对于分类、回归、序列预测等常见问题,你可以遵循一些简单的指导原则来选择正确的损失函数。
例如,对于二分类问题,你可以使用二元交叉熵(binary crossentropy)损失函数;
对于多分类问题,可以用分类交叉熵(categorical crossentropy)损失函数;
对于回归问题,可以用均方误差(mean-squared error)损失函数;
对于序列学习问题,可以用联结主义时序分类(CTC,connectionist temporal classification)损失函数,等等。
只有在面对真正全新的研究问题时,你才需要自主开发目标函数。在后面几章里,我们将详细说明对于各种常见任务应选择哪种损失函数。
3.2 Keras 简介
本书的代码示例全都使用 Keras 实现。Keras 是一个 Python 深度学习框架,可以方便地定义和训练几乎所有类型的深度学习模型。Keras 最开始是为研究人员开发的,其目的在于快速实验。
Keras 具有以下重要特性。
Keras 基于宽松的 MIT 许可证发布,这意味着可以在商业项目中免费使用它。它与所有版本的 Python 都兼容(截至 2017 年年中,从 Python 2.7 到 Python 3.6 都兼容)。
Keras 已有 200 000 多个用户,既包括创业公司和大公司的学术研究人员和工程师,也包括研究生和业余爱好者。Google、Netflix、Uber、CERN、Yelp、Square 以及上百家创业公司都在用 Keras 解决各种各样的问题。Keras 还是机器学习竞赛网站 Kaggle 上的热门框架,最新的深度学习竞赛中,几乎所有的优胜者用的都是 Keras 模型,如图 3-2 所示。
3.2.1 Keras、TensorFlow、Theano 和 CNTK
Keras 是一个模型级(model-level)的库,为开发深度学习模型提供了高层次的构建模块。它不处理张量操作、求微分等低层次的运算。相反,它依赖于一个专门的、高度优化的张量库来完成这些运算,这个张量库就是 Keras 的后端引擎(backend engine)。Keras 没有选择单个张量库并将 Keras 实现与这个库绑定,而是以模块化的方式处理这个问题(见图 3-3)。因此,几个不同的后端引擎都可以无缝嵌入到 Keras 中。目前,Keras 有三个后端实现:TensorFlow 后端、Theano 后端和微软认知工具包(CNTK,Microsoft cognitive toolkit)后端。未来 Keras 可能会扩展到支持更多的深度学习引擎。
TensorFlow、CNTK 和 Theano 是当今深度学习的几个主要平台。Theano 由蒙特利尔大学的MILA 实验室开发,TensorFlow 由 Google 开发,CNTK 由微软开发。你用 Keras 写的每一段代码都可以在这三个后端上运行,无须任何修改。也就是说,你在开发过程中可以在两个后端之间无缝切换,这通常是很有用的。例如,对于特定任务,某个后端的速度更快,那么我们就可以无缝切换过去。我们推荐使用 TensorFlow 后端作为大部分深度学习任务的默认后端,因为它的应用最广泛,可扩展,而且可用于生产环境。
通过 TensorFlow(或 Theano、CNTK),Keras 可以在 CPU 和 GPU 上无缝运行。在 CPU 上运行时,TensorFlow 本身封装了一个低层次的张量运算库,叫作 Eigen;在 GPU 上运行时,TensorFlow封装了一个高度优化的深度学习运算库,叫作 NVIDIA CUDA 深度神经网络库(cuDNN)。
3.2.2 使用 Keras 开发:概述
你已经见过一个 Keras 模型的示例,就是 MNIST 的例子。典型的 Keras 工作流程就和那个例子类似。
(1) 定义训练数据:输入张量和目标张量。
(2) 定义层组成的网络(或模型),将输入映射到目标。
(3) 配置学习过程:选择损失函数、优化器和需要监控的指标。
(4) 调用模型的 fit 方法在训练数据上进行迭代。
定义模型有两种方法:一种是使用 Sequential 类(仅用于层的线性堆叠,这是目前最常见的网络架构),另一种是函数式 API(functional API,用于层组成的有向无环图,让你可以构建任意形式的架构)。
前面讲过,这是一个利用 Sequential 类定义的两层模型(注意,我们向第一层传入了输入数据的预期形状)。
from keras import models
from keras import layers
model = models.Sequential()
model.add(layers.Dense(32, activation='relu', input_shape=(784,)))
model.add(layers.Dense(10, activation='softmax'))
下面是用函数式 API 定义的相同模型。
input_tensor = layers.Input(shape=(784,))
x = layers.Dense(32, activation='relu')(input_tensor)
output_tensor = layers.Dense(10, activation='softmax')(x)
model = models.Model(inputs=input_tensor, outputs=output_tensor)
利用函数式 API,你可以操纵模型处理的数据张量,并将层应用于这个张量,就好像这些层是函数一样。
注意 第 7 章有关于函数式 API 的详细指南。在那之前,我们的代码示例中只会用到 Sequential 类。
一旦定义好了模型架构,使用 Sequential 模型还是函数式 API 就不重要了。接下来的步骤都是相同的。
配置学习过程是在编译这一步,你需要指定模型使用的优化器和损失函数,以及训练过程中想要监控的指标。下面是单一损失函数的例子,这也是目前最常见的。
from keras import optimizers
model.compile(optimizer=optimizers.RMSprop(lr=0.001),
loss='mse',
metrics=['accuracy'])
最后,学习过程就是通过 fit() 方法将输入数据的 Numpy 数组(和对应的目标数据)传入模型,这一做法与 Scikit-Learn 及其他机器学习库类似。
model.fit(input_tensor, target_tensor, batch_size=128, epochs=10)
在接下来的几章里,你将会在这些问题上培养可靠的直觉:哪种类型的网络架构适合解决哪种类型的问题?如何选择正确的学习配置?如何调节模型使其给出你想要的结果?我们将在3.4~3.6 节讲解三个基本示例,分别是二分类问题、多分类问题和回归问题。
3.3 建立深度学习工作站
在开始开发深度学习应用之前,你需要建立自己的深度学习工作站。虽然并非绝对必要,但强烈推荐你在现代 NVIDIA GPU 上运行深度学习实验。某些应用,特别是卷积神经网络的图像处理和循环神经网络的序列处理,在 CPU 上的速度非常之慢,即使是高速多核 CPU 也是如此。即使是可以在 CPU 上运行的深度学习应用,使用现代 GPU 通常也可以将速度提高 5 倍或 10 倍。如果你不想在计算机上安装 GPU,也可以考虑在 AWS EC2 GPU 实例或 Google 云平台上运行深度学习实验。但请注意,时间一长,云端 GPU 实例可能会变得非常昂贵。
无论在本地还是在云端运行,最好都使用 UNIX 工作站。虽然从技术上来说可以在 Windows上使用 Keras(Keras 的三个后端都支持 Windows),但我们不建议这么做。在附录 A 的安装说明中,我们以安装了 Ubuntu 的计算机为例。如果你是 Windows 用户,最简单的解决方案就是安装 Ubuntu双系统。这看起来可能有点麻烦,但从长远来看,使用 Ubuntu 将会为你省去大量时间和麻烦。
注意,使用 Keras 需要安装 TensorFlow、CNTK 或 Theano(如果你希望能够在三个后端之间来回切换,那么可以安装三个)。本书将重点介绍 TensorFlow,并简要介绍一下 Theano,不会涉及 CNTK。
3.3.1 Jupyter 笔记本:运行深度学习实验的首选方法
Jupyter 笔记本是运行深度学习实验的好方法,特别适合运行本书中的许多代码示例。它广泛用于数据科学和机器学习领域。笔记本(notebook)是 Jupyter Notebook 应用生成的文件,可以在浏览器中编辑。它可以执行 Python 代码,还具有丰富的文本编辑功能,可以对代码进行注释。笔记本还可以将冗长的实验代码拆分为可独立执行的短代码,这使得开发具有交互性,而且如果后面的代码出现问题,你也不必重新运行前面的所有代码。
我们推荐使用 Jupyter 笔记本来上手 Keras,虽然这并不是必需的。你也可以运行独立的Python 脚本,或者在 IDE(比如 PyCharm)中运行代码。本书所有代码示例都以开源笔记本的形式提供,你可以在本书网站上下载:https://www.manning.com/books/deep-learning-with-python。
3.3.2 运行 Keras:两种选择
想要在实践中使用 Keras,我们推荐以下两种方式。
使用官方的 EC2 深度学习 Amazon 系统映像(AMI),并在 EC2 上以 Jupyter 笔记本的方式运行 Keras 实验。如果你的本地计算机上没有 GPU,你可以选择这种方式。附录 B 给出了详细指南。
在本地 UNIX 工作站上从头安装。然后你可以运行本地 Jupyter 笔记本或常规的 Python 代码库。如果你已经拥有了高端的 NVIDIA GPU,可以选择这种方式。附录 A 给出了基于 Ubuntu 的详细安装指南。
我们来详细看一下这两种方式的优缺点。
3.3.3 在云端运行深度学习任务:优点和缺点
如果你还没有可用于深度学习的 GPU(即最新的高端 NVIDIA GPU),那么在云端运行深度学习实验是一种简单又低成本的方法,让你无须额外购买硬件就可以上手。如果你使用Jupyter 笔记本,那么在云端运行的体验与在本地运行完全相同。截至 2017 年年中,最容易上手深度学习的云产品肯定是 AWS EC2。附录 B 给出了在 EC2 GPU 实例上运行 Jupyter 笔记本的详细指南。
但如果你是深度学习的重度用户,从长期来看这种方案是难以持续的,甚至几个星期都不行。EC2 实例的价格很高:附录 B 推荐的实例(p2.xlarge 实例,计算能力一般)在 2017 年年中的价格是每小时 0.90 美元。与此相对的是,一款可靠的消费级 GPU 价格在 1000~1500 美元——这个价格一直相当稳定,而这种 GPU 的性能则在不断提高。如果你准备认真从事深度学习,那么应该建立具有一块或多块 GPU 的本地工作站。
简而言之,EC2 是很好的上手方法。你完全可以在 EC2 GPU 实例上运行本书的代码示例。但如果你想成为深度学习的高手,那就自己买 GPU。
3.3.4 深度学习的最佳 GPU
如果你准备买一块 GPU,应该选择哪一款呢?首先要注意,一定要买 NVIDIA GPU。NVIDIA 是目前唯一一家在深度学习方面大规模投资的图形计算公司,现代深度学习框架只能在 NVIDIA 显卡上运行。截至 2017 年年中,最容易上手深度学习的云产品肯定是 AWS EC2。附录 B 给出了在 EC2 GPU 实例上运行 Jupyter 笔记本的详细指南。
从这一节开始,我们将认为你的计算机已经安装好 Keras 及其依赖,最好支持 GPU。在继续阅读之前请确认已经完成此步骤。阅读附录中的详细指南,还可以在网上搜索进一步的帮助。安装 Keras 及常见的深度学习依赖的教程有很多。
下面我们将深入讲解 Keras 示例。
3.4 电影评论分类:二分类问题
二分类问题可能是应用最广泛的机器学习问题。在这个例子中,你将学习根据电影评论的文字内容将其划分为正面或负面。
3.4.1 IMDB 数据集
本节使用 IMDB 数据集,它包含来自互联网电影数据库(IMDB)的 50 000 条严重两极分化的评论。数据集被分为用于训练的 25 000 条评论与用于测试的 25 000 条评论,训练集和测试集都包含 50% 的正面评论和 50% 的负面评论。
为什么要将训练集和测试集分开?因为你不应该将训练机器学习模型的同一批数据再用于测试模型!模型在训练数据上的表现很好,并不意味着它在前所未见的数据上也会表现得很好,而且你真正关心的是模型在新数据上的性能(因为你已经知道了训练数据对应的标签,显然不再需要模型来进行预测)。例如,你的模型最终可能只是记住了训练样本和目标值之间的映射关系,但这对在前所未见的数据上进行预测毫无用处。下一章将会更详细地讨论这一点。
与 MNIST 数据集一样,IMDB 数据集也内置于 Keras 库。它已经过预处理:评论(单词序列)
已经被转换为整数序列,其中每个整数代表字典中的某个单词。
下列代码将会加载 IMDB 数据集(第一次运行时会下载大约 80MB 的数据)。
代码清单 3-1 加载 IMDB 数据集
from keras.datasets import imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(
num_words=10000)
参数 num_words=10000 的意思是仅保留训练数据中前 10 000 个最常出现的单词。低频单词将被舍弃。这样得到的向量数据不会太大,便于处理。
train_data 和 test_data 这两个变量都是评论组成的列表,每条评论又是单词索引组成的列表(表示一系列单词)。train_labels 和 test_labels 都是 0 和 1 组成的列表,其中 0代表负面(negative),1 代表正面(positive)。
>>> train_data[0]
[1, 14, 22, 16, ... 178, 32]
>>> train_labels[0]
1
由于限定为前 10 000 个最常见的单词,单词索引都不会超过 10 000。
>>> max([max(sequence) for sequence in train_data])
9999
下面这段代码很有意思,你可以将某条评论迅速解码为英文单词。
word_index = imdb.get_word_index() #word_index 是一个将单词映射为整数索引的字典
reverse_word_index = dict(
[(value, key) for (key, value) in word_index.items()]) #键值颠倒,将整数索引映射为单词
decoded_review = ' '.join(
[reverse_word_index.get(i - 3, '?') for i in train_data[0]])
#将评论解码。注意,索引减去了 3,因为 0、1、2是为“padding”(填充)、“start of sequence”(序
#列开始)、“unknown”(未知词)分别保留的索引
3.4.2 准备数据
你不能将整数序列直接输入神经网络。你需要将列表转换为张量。转换方法有以下两种。
填充列表,使其具有相同的长度,再将列表转换成形状为 (samples, word_indices)的整数张量,然后网络第一层使用能处理这种整数张量的层(即 Embedding 层,本书后面会详细介绍)。
对列表进行 one-hot 编码,将其转换为 0 和 1 组成的向量。举个例子,序列 [3, 5] 将会被转换为 10 000 维向量,只有索引为 3 和 5 的元素是 1,其余元素都是 0。然后网络第一层可以用 Dense 层,它能够处理浮点数向量数据。
下面我们采用后一种方法将数据向量化。为了加深理解,你可以手动实现这一方法,如下所示。