深度学习研讨会(一)
原文:
annas-archive.org/md5/3c80ace5a5884b58b8fa9251691b3a28译者:飞龙
序言
关于本书
你是否对深度学习如何驱动智能应用程序感到着迷,例如自动驾驶汽车、虚拟助手、人脸识别设备和聊天机器人,用于处理数据并解决复杂问题?无论你是否熟悉机器学习,或是初学者,*《深度学习工作坊》*都将通过有趣的示例和练习,帮助你轻松理解深度学习。
本书首先强调了深度学习、机器学习和人工智能之间的关系,并通过实践练习帮助你熟悉 TensorFlow 2.0 的编程结构。你将了解神经网络、感知器的结构以及如何使用 TensorFlow 创建和训练模型。随后,本书将让你通过使用 Keras 进行图像识别练习,探索计算机视觉的基础知识。随着进展,你将能够通过实现文本嵌入和使用流行的深度学习解决方案进行数据排序,使你的模型更加强大。最后,你将掌握双向递归神经网络(RNNs),并构建生成对抗网络(GANs)用于图像合成。
在本书的最后,你将掌握使用 TensorFlow 和 Keras 构建深度学习模型所需的关键技能。
受众
如果你对机器学习感兴趣,并希望使用 TensorFlow 和 Keras 创建和训练深度学习模型,这个工作坊非常适合你。掌握 Python 及其包,并具备基本的机器学习概念,将帮助你快速学习这些主题。
章节概览
第一章,深度学习的构建块,讨论了深度学习的实际应用。一个这样的应用包括一个可以立即运行的动手代码示例,用于识别互联网上的图像。通过实践练习,你还将学习到 TensorFlow 2.0 的关键代码实现,这将帮助你在接下来的章节中构建令人兴奋的神经网络模型。
第二章,神经网络,教你人工神经网络的结构。通过使用 TensorFlow 2.0,你不仅会实现一个神经网络,还会训练它。你将建立多个不同配置的深度神经网络,从而亲身体验神经网络的训练过程。
第三章,卷积神经网络(CNNs)与图像分类,涵盖了图像处理、其工作原理以及如何将这些知识应用于卷积神经网络(CNNs)。通过实践练习,你将创建和训练 CNN 模型,用于识别手写数字甚至水果的图像。你还将学习一些关键概念,如池化层、数据增强和迁移学习。
第四章,文本的深度学习 - 嵌入层,带你进入自然语言处理的世界。你将首先进行文本预处理,这是处理原始文本数据时的一项重要技能。你将实现经典的文本表示方法,如独热编码和 TF-IDF 方法。在本章的后续部分,你将学习嵌入层,并使用 Skip-gram 和连续词袋算法生成你自己的词嵌入。
第五章,序列的深度学习,展示了如何处理一个经典的序列处理任务——股票价格预测。你将首先创建一个基于递归神经网络(RNN)的模型,然后实现一个基于 1D 卷积的模型,并将其与该 RNN 模型的表现进行比较。你将通过结合 RNN 和 1D 卷积,创建一个混合模型。
第六章,LSTM,GRU 和高级 RNN,回顾了 RNN 的实际缺点,以及长短期记忆(LSTM)模型如何帮助克服这些问题。你将构建一个分析电影评论情感的模型,并研究门控循环单元(GRU)的内部工作原理。在本章中,你将创建基于普通 RNN、LSTM 和 GRU 的模型,并在章末比较它们的表现。
第七章,生成对抗网络,介绍了生成对抗网络(GANs)及其基本组件。通过实践练习,你将使用 GANs 生成一个模拟由正弦函数生成的数据分布。你还将了解深度卷积 GANs,并在练习中实现它们。章节的后期,你将创建能够以令人信服的准确度复制图像的 GANs。
约定
文本中的代码词汇、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 用户名的显示方式如下:
"使用 mnist.load_data() 加载 MNIST 数据集"
屏幕上看到的词汇(例如,在菜单或对话框中)以相同的格式显示。
一块代码设置如下:
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
train_scaled = scaler.fit_transform(train_data)
test_scaled = scaler.transform(test_data)
新术语和重要单词的显示方式如下:
预处理的第一步不可避免是分词—将原始输入文本序列分割为词元。长代码片段会被截断,GitHub 上相应的代码文件名称会被放置在截断代码的顶部。指向完整代码的永久链接会被放置在代码片段下方。应如下所示:
Exercise7.04.ipynb
# Function to generate real samples
def realData(loc,batch):
"""
loc is the random location or mean
around which samples are centered
"""
# Generate numbers to right of the random point
xr = np.arange(loc,loc+(0.1*batch/2),0.1)
xr = xr[0:int(batch/2)]
# Generate numbers to left of the random point
xl = np.arange(loc-(0.1*batch/2),loc,0.1)
The complete code for this step can be found at https://packt.live/3iIJHVS.
代码展示
跨多行的代码通过反斜杠(\)分割。当代码执行时,Python 会忽略反斜杠,并将下一行的代码视为当前行的直接延续。
例如:
history = model.fit(X, y, epochs=100, batch_size=5, verbose=1, \
validation_split=0.2, shuffle=False)
在代码中加入注释,帮助解释特定的逻辑。单行注释使用 # 符号表示,如下所示:
# Print the sizes of the dataset
print("Number of Examples in the Dataset = ", X.shape[0])
print("Number of Features for each example = ", X.shape[1])
多行注释使用三引号括起来,如下所示:
"""
Define a seed for the random number generator to ensure the
result will be reproducible
"""
seed = 1
np.random.seed(seed)
random.set_seed(seed)
设置你的环境
在我们详细了解本书内容之前,我们需要设置一些特定的软件和工具。在接下来的部分中,我们将展示如何完成这些设置。
硬件要求
为了获得最佳的用户体验,我们推荐至少 8 GB 的内存。
在你的系统上安装 Anaconda
本书中的所有练习和活动将在 Jupyter Notebooks 中执行。要在 Windows、macOS 或 Linux 上安装 Jupyter,我们首先需要安装 Anaconda。安装 Anaconda 还将安装 Python。
-
前往
www.anaconda.com/distribution/安装 Anaconda Navigator,它是你可以访问本地 Jupyter Notebook 的界面。 -
现在,根据你的操作系统(Windows、macOS 或 Linux),你需要下载 Anaconda 安装程序。首先选择你的操作系统,然后选择 Python 版本。本书推荐使用最新版本的 Python。
图 0.1:Anaconda 主屏幕
-
要检查 Anaconda Navigator 是否正确安装,请在应用程序中查找
Anaconda Navigator。查找下面显示的图标。然而,请注意,图标的外观可能会根据你的操作系统有所不同。图 0.2 Anaconda Navigator 图标
-
点击图标打开 Anaconda Navigator。首次加载可能需要一些时间,但安装成功后,你应该会看到一个类似的界面:
图 0.3 Anaconda Navigator 图标
启动 Jupyter Notebook
要通过 Anaconda Navigator 启动 Jupyter Notebook,请按照以下步骤操作:
-
打开 Anaconda Navigator。你应该会看到如下屏幕:
图 0.4:Anaconda 安装屏幕
-
现在,点击
Jupyter Notebook面板下的Launch按钮以启动本地系统上的 notebook:
图 0.5:Jupyter Notebook 启动选项
你已经成功将 Jupyter Notebook 安装到你的系统中。你也可以通过在终端或 Anaconda 提示符中运行命令jupyter notebook来打开 Jupyter Notebook。
安装库
pip 会随 Anaconda 一起预安装。一旦 Anaconda 安装在你的计算机上,所有必需的库可以通过 pip 安装,例如 pip install numpy。或者,你也可以使用 pip install –r requirements.txt 安装所有必需的库。你可以在 packt.live/303E4dD 找到 requirements.txt 文件。
练习和活动将在 Jupyter Notebooks 中执行。Jupyter 是一个 Python 库,可以像其他 Python 库一样安装——也就是使用 pip install jupyter,但幸运的是,它已经随 Anaconda 一起预安装。要打开一个 notebook,只需在终端或命令提示符中运行 jupyter notebook 命令。
安装 TensorFlow 2.0
在安装 TensorFlow 2.0 之前,请确保你已经在系统中安装了最新版本的 pip。你可以通过以下命令检查:
pip --version
要安装 TensorFlow 2.0,你系统中的 pip 版本必须大于 19.0。你可以在 Windows、Linux 或 macOS 上使用以下命令升级 pip 版本:
pip install --upgrade pip
升级后,使用以下命令在 Windows、Linux 或 macOS 上安装 TensorFlow:
pip install --upgrade tensorflow
在 Linux 和 macOS 上,如果需要提升权限,请使用以下命令:
sudo pip install --upgrade tensorflow
注意
TensorFlow 不支持 Windows 上的 Python 2.7。
安装 Keras
要在 Windows、macOS 或 Linux 上安装 Keras,请使用以下命令:
pip install keras
在 Linux 和 macOS 上,如果需要提升权限,请使用以下命令:
sudo pip install keras
访问代码文件
你可以在 packt.live/3edmwj4 找到本书的完整代码文件。你还可以通过使用位于 packt.live/2CGCWUz 的互动实验环境,在浏览器中直接运行许多活动和练习。
我们尽力支持所有活动和练习的交互式版本,但我们也推荐进行本地安装,以防该支持不可用的情况。
注意
本书包含某些从 CSV 文件读取数据的代码片段。假设这些 CSV 文件与 Jupyter Notebook 存储在同一文件夹中。如果你将它们存储在其他位置,你需要修改路径。
如果你在安装过程中遇到任何问题或有疑问,请通过电子邮件联系我们:workshops@packt.com。
第一章:1. 深度学习的构建模块
介绍
在本章中,你将了解深度学习及其与人工智能和机器学习的关系。我们还将学习一些重要的深度学习架构,如多层感知器、卷积神经网络、递归神经网络和生成对抗网络。随着我们深入学习,你将通过实践体验 TensorFlow 框架,并使用它来实现一些线性代数操作。最后,我们将了解优化器的概念。通过利用它们来解决二次方程式,我们将理解优化器在深度学习中的作用。到本章结束时,你将对深度学习的概念和如何使用 TensorFlow 进行编程有一个清晰的了解。
介绍
你刚刚从每年的假期回来。作为一个活跃的社交媒体用户,你忙着将照片上传到你最喜欢的社交媒体应用。当照片上传后,你注意到应用会自动识别你的面部并几乎瞬间标记你。事实上,它甚至在群体照片中也能做到这一点。即使在一些光线较差的照片中,你也注意到应用大多数时候能正确标记你。那应用程序是如何学习做这些事情的呢?
要在照片中识别一个人,应用程序需要准确的信息,如此人的面部结构、骨骼结构、眼睛颜色以及许多其他细节。但当你使用这个照片应用程序时,你并不需要将所有这些细节明确地提供给应用程序。你所做的只是上传照片,应用程序就会自动开始识别你。那应用程序是如何知道这些细节的呢?
当你第一次将照片上传到应用时,应用程序会要求你标记自己。当你手动标记自己时,应用程序会自动“学习”关于你面部的所有信息。然后,每次你上传照片时,应用程序就会利用它学到的信息来识别你。当你在应用错误标记你时,手动标记自己能够帮助它改进。
该应用程序能够在最小化人工干预的情况下学习新细节并自我改进,这得益于深度学习(DL)的强大功能。深度学习是人工智能(AI)的一部分,通过识别标记数据中的模式帮助机器学习。但等一下,这不就是机器学习(ML)的功能吗?那么,深度学习和机器学习之间有什么区别呢?人工智能、机器学习和深度学习等领域之间的交集点是什么?让我们快速了解一下。
人工智能、机器学习与深度学习
人工智能是计算机科学的一个分支,旨在开发能够模拟人类智能的机器。人类智能可以简化为基于来自我们五感——视力、听力、触觉、嗅觉和味觉——的输入来做出决策。AI 并不是一个新领域,自 1950 年代以来就已有发展。此后,这个领域经历了多次高潮与低谷。进入 21 世纪,随着计算能力的飞跃、数据的丰富和对理论基础的更好理解,AI 迎来了复兴。机器学习和深度学习是 AI 的子领域,并且越来越多地被交替使用。
下图展示了 AI、ML 和 DL 之间的关系:
图 1.1:AI、ML 和 DL 之间的关系
机器学习
机器学习是 AI 的一个子集,通过识别数据中的模式并提取推论来执行特定任务。从数据中得出的推论随后用于预测未知数据的结果。机器学习与传统计算机编程在解决特定任务的方法上有所不同。在传统的计算机编程中,我们编写并执行特定的业务规则和启发式算法来获得期望的结果。然而,在机器学习中,这些规则和启发式算法并没有被明确编写。这些规则和启发式算法是通过提供数据集进行学习的。用于学习这些规则和启发式算法的数据集称为训练数据集。整个学习和推断的过程称为训练。
学习规则和启发式算法是通过使用不同的算法来完成的,这些算法采用统计模型来实现这一目的。这些算法利用多种数据表示方式进行学习。每种数据的表示方式称为示例。示例中的每个元素称为特征。以下是著名的 IRIS 数据集的一个示例(archive.ics.uci.edu/ml/datasets/Iris)。该数据集表示了不同种类的鸢尾花,基于不同的特征,如萼片和花瓣的长度与宽度:
图 1.2:IRIS 数据集的样本数据
在前面的数据集中,每一行数据代表一个例子,每一列是一个特征。机器学习算法利用这些特征从数据中推断出结论。模型的准确性,以及预测结果的可靠性,很大程度上依赖于数据的特征。如果提供给机器学习算法的特征能够很好地代表问题陈述,那么得到好结果的机会就会很高。一些常见的机器学习算法包括线性回归、逻辑回归、支持向量机、随机森林和XGBoost。
尽管传统的机器学习算法在许多应用场景中都很有用,但它们在获得优异结果时,非常依赖于特征的质量。特征的创建是一门耗时的艺术,且需要大量的领域知识。然而,即便拥有全面的领域知识,仍然存在将这些知识转化为特征的局限性,进而无法很好地封装数据生成过程中的细微差别。此外,随着机器学习所解决问题的复杂性增加,特别是非结构化数据(如图像、语音、文本等)的出现,几乎不可能创建能够表示复杂函数的特征,这些复杂函数反过来又生成数据。因此,往往需要找到一种不同的方法来解决复杂问题,这时深度学习就派上了用场。
深度学习
深度学习是机器学习的一个子集,是一种称为人工神经网络(ANN)的算法的扩展。神经网络并不是一种新现象。神经网络的创建可以追溯到 20 世纪 40 年代的上半期。神经网络的开发灵感来自于对人类大脑运作方式的了解。从那时起,这一领域经历了几次高潮和低谷。一个重新激发人们对神经网络兴趣的关键时刻是由该领域的巨头们,如 Geoffrey Hinton,提出的反向传播算法。正因为如此,Hinton 被广泛认为是“深度学习的教父”。我们将在第二章《神经网络》中深入讨论神经网络。
多层(深层)人工神经网络(ANNs)是深度学习的核心。深度学习模型的一个显著特点是其能够从输入数据中学习特征。与传统的机器学习不同,后者需要手动创建特征,深度学习擅长从多个层次学习不同的特征层级。例如,假设我们使用一个深度学习模型来检测人脸。模型的初始层会学习面部的低级近似特征,如面部的边缘,如图 1.3所示。每个后续层会将前一层的特征组合起来,形成更复杂的特征。在人脸检测的例子中,如果初始层学会了检测边缘,后续层将这些边缘组合起来,形成面部的一部分,如鼻子或眼睛。这个过程在每一层继续进行,直到最后一层生成一个完整的人脸图像:
图 1.3:用于检测人脸的深度学习模型
注意
上述图片来自于一篇流行的研究论文:Lee, Honglak & Grosse, Roger & Ranganath, Rajesh & Ng, Andrew. (2011). 无监督学习层次表示与卷积深度置信网络. Commun. ACM. 54. 95-103. 10.1145/2001269.2001295.
深度学习技术在过去十年中取得了巨大的进步。多个因素促使了深度学习技术的指数增长,其中最重要的因素是大量数据的可用性。数字时代,随着越来越多设备的互联,产生了大量数据,特别是非结构化数据。这反过来促进了深度学习技术的大规模应用,因为它们非常适合处理大量的非结构化数据。
深度学习崛起的另一个重要因素是计算基础设施的进步。深度学习模型通常包含大量层次和数百万个参数,因此需要强大的计算能力。图形处理单元(GPU)和张量处理单元(TPU)等计算层次的进步,以合理的成本提供了强大的计算能力,从而推动了深度学习的广泛应用。
深度学习的普及还得益于不同框架的开源,这些框架用于构建和实现深度学习模型。2015 年,Google Brain 团队开源了 TensorFlow 框架,自那时以来,TensorFlow 已成长为最受欢迎的深度学习框架之一。其他主要的框架包括 PyTorch、MXNet 和 Caffe。本书将使用 TensorFlow 框架。
在我们深入探讨深度学习的构建块之前,让我们通过一个简短的演示来实际体验深度学习模型的强大功能。你不需要了解演示中的所有代码。只需按照指示操作,你就能快速了解深度学习的基本能力。
使用深度学习分类图像
在接下来的练习中,我们将分类一个披萨的图像,并将分类结果的文本转换为语音。为了对图像进行分类,我们将使用一个预训练的模型。文本转语音将使用一个免费提供的 API——Google 文本转语音(gTTS)来完成。在开始之前,让我们先了解一些这个演示的关键构建块。
预训练模型
训练一个深度学习模型需要大量的计算资源和时间,并且需要庞大的数据集。然而,为了促进研究和学习,深度学习社区也提供了在大数据集上训练好的模型。这些预训练模型可以下载并用于预测,或者用于进一步训练。在本次演示中,我们将使用一个名为ResNet50的预训练模型。这个模型与 Keras 包一起提供。这个预训练模型能够预测我们日常生活中遇到的 1,000 种不同类型的物体,比如鸟类、动物、汽车等。
Google 文本转语音 API
Google 已经将其文本转语音算法开放供有限使用。我们将使用这个算法将预测的文本转换为语音。
演示所需的先决条件包
为了让这个演示正常工作,你需要在机器上安装以下包:
-
TensorFlow 2.0
-
Keras
-
gTTS
请参考前言以了解安装前两个包的过程。安装 gTTS 将在练习中展示。接下来,让我们深入了解演示。
练习 1.01:图像和语音识别演示
在本次练习中,我们将演示使用深度学习模型进行图像识别和语音转文本的转换。此时,你可能无法理解代码中的每一行,这将在后续讲解中解释。现在,只需执行代码,了解使用 TensorFlow 构建深度学习和人工智能应用程序有多么简单。按照以下步骤完成本次练习:
-
打开一个 Jupyter Notebook 并命名为练习 1.01。关于如何启动 Jupyter Notebook 的详细信息,请参阅前言。
-
导入所有必需的库:
from tensorflow.keras.preprocessing.image import load_img from tensorflow.keras.preprocessing.image import img_to_array from tensorflow.keras.applications.resnet50 import ResNet50 from tensorflow.keras.preprocessing import image from tensorflow.keras.applications.resnet50 \ import preprocess_input from tensorflow.keras.applications.resnet50 \ import decode_predictions \ ) to split the logic across multiple lines. When the code is executed, Python will ignore the backslash, and treat the code on the next line as a direct continuation of the current line.这里简要描述我们将要导入的包:
load_img:将图像加载到 Jupyter Notebook 中img_to_array:将图像转换为 NumPy 数组,这是 Keras 所需的格式preprocess_input:将输入转换为模型可以接受的格式decode_predictions:将模型预测的数值输出转换为文本标签Resnet50:这是一个预训练的图像分类模型 -
创建一个预训练的
Resnet模型实例:mymodel = ResNet50()下载过程中你应收到类似以下的消息:
图 1.4:加载 Resnet50
Resnet50是一个预训练的图像分类模型。对于首次使用者,下载模型到你的环境中需要一些时间。 -
从互联网上下载一张披萨的图片,并将其保存在运行 Jupyter Notebook 的同一文件夹中。将图片命名为
im1.jpg。注意
你还可以通过此链接下载我们使用的图片:
packt.live/2AHTAC9 -
使用以下命令加载待分类的图片:
myimage = load_img('im1.jpg', target_size=(224, 224))如果你将图片保存在另一个文件夹中,则必须提供图片所在位置的完整路径,代替
im1.jpg命令。例如,如果图片保存在D:/projects/demo中,代码应如下所示:myimage = load_img('D:/projects/demo/im1.jpg', \ target_size=(224, 224)) -
我们通过以下命令来显示图片:
myimage上述命令的输出将如下所示:
图 1.5:加载图片后显示的输出
-
将图片转换为
numpy数组,因为模型期望它是这种格式:myimage = img_to_array(myimage) -
将图片调整为四维格式,因为这是模型期望的格式:
myimage = myimage.reshape((1, 224, 224, 3)) -
通过运行
preprocess_input()函数准备图片以供提交:myimage = preprocess_input(myimage) -
运行预测:
myresult = mymodel.predict(myimage) -
预测结果是一个数字,需要将其转换为相应的文本格式标签:
mylabel = decode_predictions(myresult) -
接下来,键入以下代码以显示标签:
mylabel = mylabel[0][0] -
使用以下代码打印标签:
print("This is a : " + mylabel[1])如果到目前为止你已正确按照步骤操作,输出结果将如下所示:
This is a : pizza模型已成功识别我们的图片。很有趣,不是吗?接下来的几个步骤,我们将进一步处理,将这个结果转化为语音。
小贴士
虽然我们在这里使用了一张披萨的图片,但你可以使用任何图片来进行模型测试。我们建议你多次尝试使用不同的图片进行此练习。
-
准备要转换为语音的文本:
sayit="This is a "+mylabel[1] -
安装
gtts包,该包用于将文本转换为语音。可以在 Jupyter Notebook 中按如下方式实现:!pip install gtts -
导入所需的库:
from gtts import gTTS import os上述代码将导入两个库。一个是
gTTS,即 Google 文本转语音服务,这是一个基于云的开源 API,用于将文本转换为语音。另一个是os库,用于播放生成的音频文件。 -
调用
gTTSAPI 并将文本作为参数传递:myobj = gTTS(text=sayit)注意
运行上述步骤时,你需要保持在线状态。
-
保存生成的音频文件。该文件将保存在运行 Jupyter Notebook 的主目录中。
myobj.save("prediction.mp3")注意
你还可以通过在文件名之前指定绝对路径来设置保存位置;例如,
(myobj.save('D:/projects/prediction.mp3')。 -
播放音频文件:
os.system("prediction.mp3")如果你正确地遵循了前面的步骤,你将听到
This is a pizza的语音。注
要访问此特定部分的源代码,请参考
packt.live/2ZPZx8B。你也可以在
packt.live/326cRIu在线运行这个示例。你必须执行整个笔记本才能获得预期的结果。
在这个练习中,我们学习了如何通过使用公共可用的模型并用几行代码在 TensorFlow 中构建深度学习模型。现在你已经体验了深度学习,让我们继续前进,了解深度学习的不同构建块。
深度学习模型
大多数流行的深度学习模型的核心是人工神经网络(ANN),其灵感来源于我们对大脑工作原理的认识。虽然没有任何单一模型可以称为完美,但不同的模型在不同的场景下表现更好。在接下来的章节中,我们将了解一些最突出的模型。
多层感知器
多层感知器(MLP)是一种基本的神经网络类型。MLP 也被称为前馈网络。以下图所示可以看到 MLP 的表示:
图 1.6:MLP 表示
多层感知器(MLP,或任何神经网络)的基本构建块之一是神经元。一个网络由多个神经元连接到后续的层。非常基础的 MLP 由输入层、隐藏层和输出层组成。输入层的神经元数量与输入数据相等。每个输入神经元将与隐藏层的所有神经元相连接。最终的隐藏层将与输出层连接。MLP 是一个非常有用的模型,可以尝试应用于各种分类和回归问题。MLP 的概念将在第二章,神经网络中详细介绍。
卷积神经网络
卷积神经网络(CNN)是一类深度学习模型,主要用于图像识别。当我们讨论 MLP 时,我们看到每一层的神经元都与后续层的每个神经元相连接。然而,CNN 采用了不同的方法,并没有使用这种完全连接的架构。相反,CNN 从图像中提取局部特征,然后将这些特征传递到后续层。
CNN 在 2012 年崭露头角,当时名为 AlexNet 的架构在一个名为ImageNet 大规模视觉识别挑战赛(ILSVRC)的顶级竞赛中获胜。ILSVRC 是一个大规模计算机视觉竞赛,全球各地的团队竞相争夺最佳计算机视觉模型的奖项。在 2012 年的研究论文《ImageNet 分类与深度卷积神经网络》(papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks)中,Alex Krizhevsky 等人(多伦多大学)展示了 CNN 架构的真正强大力量,最终赢得了 2012 年 ILSVRC 挑战赛。下图展示了AlexNet模型的结构,这是一个 CNN 模型,其卓越的性能使得 CNN 在深度学习领域声名鹊起。尽管这个模型的结构看起来可能对你来说比较复杂,但在第三章《卷积神经网络图像分类》中,这种 CNN 网络的工作原理会被详细讲解:
图 1.7:AlexNet 模型的 CNN 架构
注意
上述图表来源于著名的研究论文:Krizhevsky, Alex & Sutskever, Ilya & Hinton, Geoffrey. (2012). ImageNet 分类与深度卷积神经网络。神经信息处理系统。25. 10.1145/3065386。
自 2012 年以来,许多突破性的 CNN 架构扩展了计算机视觉的可能性。一些著名的架构有 ZFNet、Inception(GoogLeNet)、VGG 和 ResNet。
CNN 应用最为显著的一些用例如下:
-
图像识别和光学字符识别(OCR)
-
社交媒体上的人脸识别
-
文本分类
-
自动驾驶汽车的物体检测
-
医疗健康领域的图像分析
使用深度学习的另一个巨大好处是,你不必总是从零开始构建模型——你可以使用他人已经构建的模型,并将其用于自己的应用。这就是所谓的“迁移学习”,它使你能够从活跃的深度学习社区中受益。
我们将在第三章《卷积神经网络图像分类》中应用迁移学习于图像处理,并详细了解 CNN 及其动态。
循环神经网络
在传统的神经网络中,输入与输出是相互独立的。然而,在语言翻译等场景中,单词前后存在依赖关系,因此需要理解单词出现顺序的动态特性。这个问题通过一种被称为循环神经网络(RNNs)的网络类别得到了解决。RNNs 是一类深度学习网络,其中前一步的输出作为当前步骤的输入。RNN 的一个显著特点是隐藏层,它能够记住序列中其他输入的信息。以下图可以看到 RNN 的高级表示。你将在第五章,深度学习与序列中深入了解这些网络的内部工作原理:
图 1.8:RNN 的结构
RNN 架构有不同的类型。其中一些最著名的类型是长短时记忆网络(LSTM)和门控循环单元(GRU)。
RNN 的一些重要应用案例如下:
-
语言建模与文本生成
-
机器翻译
-
语音识别
-
生成图像描述
RNN 将在第五章,深度学习与序列和第六章,LSTMs、GRUs 及高级 RNN中详细讲解。
生成对抗网络
生成对抗网络(GANs)是一种能够生成与任何真实数据分布相似的数据分布的网络。深度学习的先驱之一 Yann LeCun 曾表示,GANs 是过去十年中深度学习领域最具前景的想法之一。
举个例子,假设我们想从随机噪声数据生成狗的图像。为此,我们训练一个 GAN 网络,使用真实的狗的图像和噪声数据,直到我们生成的图像看起来像真实的狗的图像。以下图解释了 GAN 的基本概念。在这个阶段,你可能还不完全理解这个概念。它将在第七章,生成对抗网络中详细讲解。
图 1.9:GANs 的结构
注意
上述图表来源于一篇流行的研究论文:Barrios, Buldain, Comech, Gilbert & Orue (2019)。利用深度学习方法进行局部放电分类——最近进展综述(doi.org/10.3390/en12132485)。
GANs 是一个重要的研究领域,并且有许多应用案例。以下是一些 GANs 的有用应用:
-
图像翻译
-
文本到图像合成
-
生成视频
-
艺术修复
GANs 将在第七章,生成对抗网络中详细讲解。
深度学习的可能性和前景是巨大的。深度学习应用已无处不在,成为我们日常生活的一部分。以下是一些显著的例子:
-
聊天机器人
-
机器人
-
智能音响(例如 Alexa)
-
虚拟助手
-
推荐引擎
-
无人机
-
自动驾驶汽车或自动化车辆
这种不断扩展的可能性画布使它成为数据科学家工具箱中的一个重要工具。本书将逐步引导你进入深度学习的奇妙世界,并使你能够将其应用于现实世界的场景。
TensorFlow 简介
TensorFlow 是由 Google 开发的深度学习库。在撰写本书时,TensorFlow 是迄今为止最流行的深度学习库。最初,它由 Google 内部的一个团队——Google Brain 团队开发,用于内部使用,并于 2015 年开源。Google Brain 团队开发了像 Google Photos 和 Google Cloud Speech-to-Text 这样的流行应用,这些应用基于 TensorFlow,属于深度学习应用。TensorFlow 1.0 于 2017 年发布,并在短时间内超越了其他现有的库,如 Caffe、Theano 和 PyTorch,成为最受欢迎的深度学习库。它被认为是行业标准,几乎每个从事深度学习的组织都在使用它。TensorFlow 的一些关键特点如下:
-
它可以与所有常见的编程语言一起使用,如 Python、Java 和 R。
-
它可以部署在多个平台上,包括 Android 和 Raspberry Pi。
-
它可以以高度分布的模式运行,因此具有高度的可扩展性。
在经历了长时间的 Alpha/Beta 发布后,TensorFlow 2.0 的最终版本于 2019 年 9 月 30 日发布。TF2.0 的重点是使深度学习应用的开发更加简便。接下来我们将一起了解 TensorFlow 2.0 框架的基础知识。
张量
在 TensorFlow 程序中,每个数据元素都叫做张量。张量是向量和矩阵在更高维度下的表示。张量的秩表示其维度。以下是一些常见的数据形式,以张量的形式表示:
标量
标量是秩为 0 的张量,它只有大小。
例如,[ 12 ] 是一个大小为 12 的标量。
向量
向量是秩为 1 的张量。
例如,[ 10 , 11, 12, 13]。
矩阵
矩阵是秩为 2 的张量。
例如,[ [10,11] , [12,13] ]。这个张量有两行两列。
秩为 3 的张量
这是一个三维张量。例如,图像数据通常是一个三维张量,具有宽度、高度和通道数作为其三个维度。以下是一个三维张量的例子,即它有两行、三列和三个通道:
图 1.10:三维张量
张量的形状由一个数组表示,表示每个维度中的元素个数。例如,如果一个张量的形状是 [2,3,5],这意味着该张量有三个维度。如果这是图像数据,则此形状表示该张量有两行、三列和五个通道。我们还可以从形状中获取秩。在这个例子中,张量的秩是三,因为有三个维度。下面的图示进一步说明了这一点:
图 1.11:张量的秩和形状示例
常量
常量用于存储在程序执行过程中不会被改变或修改的值。创建常量有多种方式,最简单的一种如下:
a = tf.constant (10)
这将创建一个初始化为 10 的张量。请记住,常量的值不能通过重新赋值来更新或修改。另一个示例如下:
s = tf.constant("Hello")
在这一行中,我们正在将一个字符串实例化为常量。
变量
变量用于存储在程序执行过程中可以更新和修改的数据。我们将在第二章,神经网络中详细讨论这一点。创建变量有多种方式,最简单的一种如下:
b=tf.Variable(20)
在前面的代码中,变量b被初始化为20。请注意,在 TensorFlow 中,与常量不同,Variable 这个术语的首字母是大写的。
变量可以在程序执行过程中重新赋予不同的值。变量可以用于赋值任何类型的对象,包括标量、向量和多维数组。以下是如何在 TensorFlow 中创建一个维度为 3 x 3 的数组的示例:
C = tf.Variable([[1,2,3],[4,5,6],[7,8,9]])
这个变量可以初始化为一个 3 x 3 的矩阵,如下所示:
图 1.12:3 x 3 矩阵
现在我们已经了解了 TensorFlow 的一些基本概念,接下来让我们学习如何将它们付诸实践。
在 TensorFlow 中定义函数
在 Python 中可以使用以下语法创建函数:
def myfunc(x,y,c):
Z=x*x*y+y+c
return Z
使用特殊运算符def来初始化一个函数,接着是函数的名称myfunc,以及函数的参数。在前面的示例中,函数体位于第二行,最后一行返回输出。
在接下来的练习中,我们将学习如何使用之前定义的变量和常量来实现一个简单的函数。
练习 1.02:实现一个数学方程
在本练习中,我们将使用 TensorFlow 求解以下数学方程:
图 1.13:使用 TensorFlow 求解的数学方程
我们将使用 TensorFlow 来求解它,如下所示:
X=3
Y=4
虽然有多种方法可以实现这一点,但在本练习中我们只会探索其中的一种方法。按照以下步骤完成此练习:
-
打开一个新的 Jupyter Notebook,并将其重命名为Exercise 1.02。
-
使用以下命令导入 TensorFlow 库:
import tensorflow as tf -
现在,让我们解这个方程。为此,你需要创建两个变量,
X和Y,并分别将它们初始化为给定的值3和4:X=tf.Variable(3) Y=tf.Variable(4) -
在我们的方程中,
2的值没有变化,因此我们将它作为常量存储,代码如下:C=tf.constant(2) -
定义一个函数来解决我们的方程:
def myfunc(x,y,c): Z=x*x*y+y+c return Z -
通过传递
X,Y和C作为参数来调用该函数。我们将把该函数的输出存储在一个名为result的变量中:result=myfunc(X,Y,C) -
使用
tf.print()函数打印结果:tf.print(result)输出结果如下:
42注意
若要访问该部分的源代码,请参阅
packt.live/2ClXKjj。你也可以在
packt.live/2ZOIN1C上运行这个示例。你必须执行整个 Notebook 才能得到预期的结果。
在这个练习中,我们学习了如何定义和使用一个函数。熟悉 Python 编程的人会注意到,这与正常的 Python 代码没有太大区别。
在本章的其余部分,我们将通过学习一些基本的线性代数,并熟悉一些常见的向量运算,为下一章的神经网络做准备,这样理解神经网络就会更加容易。
使用 TensorFlow 进行线性代数
在神经网络中使用的最重要的线性代数主题是矩阵乘法。在这一节中,我们将解释矩阵乘法的原理,并使用 TensorFlow 的内置函数解决一些矩阵乘法的示例。这对下一章神经网络的准备工作至关重要。
矩阵乘法是如何工作的?你可能在高中时学过这个内容,但让我们快速回顾一下。
假设我们需要执行两个矩阵 A 和 B 之间的矩阵乘法,其中我们有以下内容:
图 1.14:矩阵 A
图 1.15:矩阵 B
第一步是检查一个 2x3 的矩阵乘以一个 3x2 的矩阵是否可能。矩阵乘法有一个前提条件。记住 C=R,即第一个矩阵的列数(C)应当等于第二个矩阵的行数(R)。并且要记住顺序很重要,这也是为什么 A x B 不等于 B x A。在这个例子中,C=3,R=3。所以,乘法是可能的。
结果矩阵的行数将与 A 相同,列数将与 B 相同。因此,在这种情况下,结果将是一个 2x2 的矩阵。
要开始乘法运算,取 A 的第一行(R1)和 B 的第一列(C1)的元素:
图 1.16:矩阵 A(R1)
图 1.17:矩阵 B(C1)
获取按元素乘积的和,即 (1 x 7) + (2 x 9) + (3 x 11) = 58。这个将是结果 2 x 2 矩阵中的第一个元素。我们暂时称这个为不完整矩阵 D(i):
图 1.18:不完整矩阵 D(i)
重复此操作,使用 A 的第一行(R1)和 B 的第二列(C2):
图 1.19:矩阵 A 的第一行
图 1.20:矩阵 B 的第二列
获取对应元素的乘积之和,即 (1 x 8) + (2 x 10) + (3 x 12) = 64。这个将是结果矩阵中的第二个元素:
图 1.21:矩阵 D(i) 的第二个元素
使用第二行重复相同的操作,以得到最终结果:
图 1.22:矩阵 D
相同的矩阵乘法可以通过 TensorFlow 中的内置方法 tf.matmul() 来执行。需要相乘的矩阵必须作为变量传递给模型,如下例所示:
C = tf.matmul(A,B)
在前面的例子中,A 和 B 是我们要进行乘法运算的矩阵。我们通过使用 TensorFlow 来练习这个方法,进行我们手动计算过的两个矩阵的乘法。
练习 1.03:使用 TensorFlow 进行矩阵乘法
在这个练习中,我们将使用 tf.matmul() 方法通过 tensorflow 进行两个矩阵的乘法运算。按照以下步骤完成此练习:
-
打开一个新的 Jupyter Notebook,并将其重命名为 Exercise 1.03。
-
导入
tensorflow库并创建两个变量X和Y,它们是矩阵。X是一个 2 x 3 的矩阵,Y是一个 3 x 2 的矩阵:import tensorflow as tf X=tf.Variable([[1,2,3],[4,5,6]]) Y=tf.Variable([[7,8],[9,10],[11,12]]) -
打印并显示
X和Y的值,确保矩阵正确创建。我们首先打印X的值:tf.print(X)输出结果如下:
[[1 2 3] [4 5 6]]现在,让我们打印
Y的值:tf.print(Y)输出结果如下:
[[7 8] [9 10] [11 12]] -
通过调用 TensorFlow 的
tf.matmul()函数执行矩阵乘法:c1=tf.matmul(X,Y)为了显示结果,打印
c1的值:tf.print(c1)输出结果如下:
[[58 64] [139 154]] -
让我们通过改变矩阵的顺序来执行矩阵乘法:
c2=tf.matmul(Y,X)为了显示结果,让我们打印
c2的值:tf.print(c2)结果输出如下:
[[39 54 69] [49 68 87] [59 82 105]]请注意,由于我们改变了顺序,结果不同。
注意
要访问该特定部分的源代码,请参考
packt.live/3eevyw4。你还可以在线运行这个示例,网址为
packt.live/2CfGGvE。你必须执行整个 Notebook 才能得到预期的结果。
在这个练习中,我们学习了如何在 TensorFlow 中创建矩阵,以及如何执行矩阵乘法。这在我们创建自己的神经网络时将会非常有用。
reshape 函数
如名称所示,reshape 函数可以改变张量的形状,将其从当前形状转换为新的形状。例如,你可以将一个 2 × 3 的矩阵重塑为 3 × 2 的矩阵,如下所示:
图 1.23:重塑后的矩阵
让我们考虑以下 2 × 3 的矩阵,它是我们在前一个练习中定义的:
X=tf.Variable([[1,2,3],[4,5,6]])
我们可以使用以下代码打印矩阵的形状:
X.shape
从以下输出中,我们可以看到形状,这是我们已经知道的:
TensorShape([2, 3])
现在,要将 X 重塑为一个 3 × 2 的矩阵,TensorFlow 提供了一个方便的函数,叫做 tf.reshape()。该函数通过以下参数来实现:
tf.reshape(X,[3,2])
在前面的代码中,X 是需要重塑的矩阵,[3,2] 是 X 矩阵需要重塑成的新形状。
重塑矩阵是实现神经网络时常用的操作。例如,在使用 CNN 处理图像时,图像必须是 3 维的,也就是说,它必须有三个维度:宽度、高度和深度。如果我们的图像是一个只有两个维度的灰度图像,那么 reshape 操作就能派上用场,来添加第三个维度。在这种情况下,第三个维度的大小将是 1:
图 1.24:使用 reshape() 改变维度
在前面的图中,我们将一个形状为 [5,4] 的矩阵重新调整为形状为 [5,4,1] 的矩阵。在接下来的练习中,我们将使用 reshape() 函数将一个 [5,4] 矩阵进行重塑。
在实现 reshape() 函数时,有一些重要的注意事项:
-
新形状中的元素总数应与原始形状中的元素总数相等。例如,你可以将一个 2 × 3 的矩阵(总共 6 个元素)重塑为 3 × 2 的矩阵,因为新形状也有 6 个元素。但是,你不能将它重塑为 3 × 3 或 3 × 4。
-
reshape()函数不应与transpose()混淆。在reshape()中,矩阵元素的顺序保持不变,元素在新形状中按照相同的顺序重新排列。然而,在transpose()的情况下,行变成列,列变成行。因此,元素的顺序会发生变化。 -
reshape()函数不会改变原始矩阵,除非你将新形状赋值给它。否则,它只是显示新形状,而并不会实际更改原始变量。例如,假设x的形状是 [2,3],你只是执行了tf.reshape(x,[3,2])。当你再次检查x的形状时,它依然是 [2,3]。为了实际改变形状,你需要将新形状赋值给它,像这样:x=tf.reshape(x,[3,2])
让我们在接下来的练习中尝试在 TensorFlow 中实现 reshape()。
练习 1.04:使用 TensorFlow 中的 reshape() 函数重塑矩阵
在本练习中,我们将使用reshape()函数将一个[5,4]的矩阵重塑为[5,4,1]的形状。这个练习将帮助我们理解如何使用reshape()来改变张量的秩。按照以下步骤完成此练习:
-
打开一个 Jupyter Notebook 并将其重命名为练习 1.04。然后,导入
tensorflow并创建我们想要重塑的矩阵:import tensorflow as tf A=tf.Variable([[1,2,3,4], \ [5,6,7,8], \ [9,10,11,12], \ [13,14,15,16], \ [17,18,19,20]]) -
首先,我们将打印变量
A,以检查它是否已正确创建,使用以下命令:tf.print(A)输出结果如下:
[[1 2 3 4] [5 6 7 8] [9 10 11 12] [13 14 15 16] [17 18 19 20]] -
让我们打印一下
A的形状,以确保正确:A.shape输出结果如下:
TensorShape([5, 4])当前它的秩是 2。我们将使用
reshape()函数将其秩更改为 3。 -
现在,我们将使用以下命令将
A重塑为形状[5,4,1]。我们加入了print命令,以便查看输出结果:tf.print(tf.reshape(A,[5,4,1]))我们将得到以下输出:
[[[1] [2] [3] [4]] [[5] [6] [7] [8]] [[9] [10] [11] [12]] [[13] [14] [15] [16]] [[17] [18] [19] [20]]]这按预期工作。
-
让我们看看
A的新形状:A.shape输出结果如下:
TensorShape([5, 4])我们可以看到
A仍然具有相同的形状。记得我们讨论过,为了保存新的形状,我们需要将其赋值给自己。我们将在下一步中执行此操作。 -
在这里,我们将新的形状赋给
A:A = tf.reshape(A,[5,4,1]) -
让我们再检查一次
A的新形状:A.shape我们将看到以下输出:
TensorShape([5, 4, 1])到目前为止,我们不仅重新塑造了矩阵,还将其秩从 2 更改为 3。在下一步中,让我们打印出
A的内容,以确保无误。 -
让我们看看
A现在包含了什么:tf.print(A)输出结果,如预期的那样,将如下所示:
[[[1] [2] [3] [4]] [[5] [6] [7] [8]] [[9] [10] [11] [12]] [[13] [14] [15] [16]] [[17] [18] [19] [20]]]注意
要访问此特定部分的源代码,请参考
packt.live/3gHvyGQ。您也可以在
packt.live/2ZdjdUY上在线运行这个例子。您必须执行整个 Notebook 才能获得所需的结果。
在本练习中,我们学习了如何使用reshape()函数。通过reshape(),我们可以改变张量的秩和形状。我们还了解到,重塑矩阵会改变矩阵的形状,但不会改变矩阵中元素的顺序。另一个我们学到的重要内容是,重塑的维度必须与矩阵中的元素数量对齐。了解了reshape函数后,我们将继续学习下一个函数——Argmax。
argmax 函数
现在,让我们了解argmax函数,它在神经网络中经常使用。argmax返回矩阵或张量沿某个特定轴的最大值位置。需要注意的是,它并不会返回最大值本身,而是返回最大值的索引位置。
例如,如果x = [1,10,3,5],那么tf.argmax(x)将返回 1,因为最大值(在这种情况下是 10)位于索引位置 1。
注意
在 Python 中,索引是从 0 开始的。所以,考虑到前面的x例子,元素 1 的索引为 0,10 的索引为 1,依此类推。
现在,假设我们有以下内容:
图 1.25:示例矩阵
在这种情况下,argmax必须与axis参数一起使用。当axis等于 0 时,它返回每列中最大值的位置,如下图所示:
图 1.26:沿轴 0 进行的 argmax 操作
如您所见,第一列的最大值是 9,因此在这种情况下,索引为 2。同样,若我们查看第二列,最大值是 5,其索引为 0。在第三列,最大值为 8,因此索引为 1。如果我们在前述矩阵上运行argmax函数并将axis设置为 0,我们将得到以下输出:
[2,0,1]
当axis = 1 时,argmax返回每行最大值的位置,如下所示:
图 1.27:沿轴 1 进行的 argmax 操作
沿着行移动,我们在索引 1 处有 5,在索引 2 处有 8,在索引 0 处有 9。如果我们在前述矩阵上运行argmax函数,并将axis设置为 1,我们将得到以下输出:
[1,2,0]
现在,让我们尝试在矩阵上实现argmax。
练习 1.05:实现 argmax()函数
在这个练习中,我们将使用argmax函数在给定矩阵的轴 0 和轴 1 上找到最大值的位置。请按照以下步骤完成此练习:
-
导入
tensorflow并创建以下矩阵:import tensorflow as tf X=tf.Variable([[91,12,15], [11,88,21],[90, 87,75]]) -
让我们打印
X并查看矩阵的样子:tf.print(X)输出将如下所示:
[[91 12 15] [11 88 21] [90 87 75]] -
打印
X的形状:X.shape输出将如下所示:
TensorShape([3, 3]) -
现在,让我们使用
argmax在保持axis为0的情况下找到最大值的位置:tf.print(tf.argmax(X,axis=0))输出将如下所示:
[0 1 2]参考步骤 2中的矩阵,我们可以看到,沿着列移动,第一列中最大值(91)的索引是 0。同样,第二列中最大值(88)的索引是 1。最后,第三列中最大值(75)的索引是 2。因此,我们得到了上述输出。
-
现在,让我们将
axis改为1:tf.print(tf.argmax(X,axis=1))输出将如下所示:
[0 1 0]
再次参考步骤 2中的矩阵,如果我们沿着行移动,第一行中的最大值是 91,索引为 0。同样,第二行中的最大值是 88,索引为 1。最后,第三行的最大值是 75,索引又是 0。
注意
要访问此特定部分的源代码,请参阅packt.live/2ZR5q5p。
您还可以在packt.live/3eewhNO在线运行此示例。您必须执行整个 Notebook 才能获得所需的结果。
在本次练习中,我们学习了如何使用argmax函数来找到张量给定轴上最大值的位置。这将在后续章节中用于使用神经网络进行分类时。
优化器
在我们研究神经网络之前,让我们先了解另一个重要的概念,那就是优化器。优化器广泛应用于训练神经网络,因此理解其应用非常重要。在本章中,让我们对优化器的概念做一个基本的介绍。正如你可能已经知道的,机器学习的目的是找到一个函数(以及它的参数),该函数将输入映射到输出。
举个例子,假设一个数据分布的原始函数是以下形式的线性函数(线性回归):
Y = mX + b
在这里,Y是因变量(标签),X是自变量(特征),m和b是模型的参数。使用机器学习解决这个问题就是学习m和b这两个参数,从而得出将X与Y联系起来的函数形式。一旦这些参数被学习到,如果我们给定一个新的X值,我们就可以计算或预测Y的值。在学习这些参数的过程中,优化器发挥了作用。学习过程包括以下几个步骤:
-
假设
m和b是一些任意的随机值。 -
在这些假设的参数下,对于给定的数据集,估算每个
X变量的Y值。 -
找到
Y的预测值和与X变量相关的实际值之间的差异。这个差异称为损失函数或代价函数。损失的大小将取决于我们最初假设的参数值。如果假设与实际值相差甚远,那么损失就会很大。通过改变或调整参数的初始假设值,使得损失函数最小化,就能接近正确的参数。这一改变参数值以减少损失函数的过程称为优化。
在深度学习中,有不同类型的优化器。一些最常用的优化器包括随机梯度下降、Adam 和 RMSprop。优化器的详细功能和内部工作原理将在第二章,神经网络中进行描述,但在这里,我们将看到它们如何应用于解决一些常见问题,比如简单线性回归。在本章中,我们将使用一个非常流行的优化器——Adam。我们可以使用以下代码在 TensorFlow 中定义 Adam 优化器:
tf.optimizers.Adam()
一旦定义了优化器,我们可以使用以下代码来最小化损失:
optimizer.minimize(loss,[m,b])
[m,b]是优化过程中会被改变的参数。现在,让我们使用优化器通过 TensorFlow 训练一个简单的线性回归模型。
练习 1.06:使用优化器进行简单线性回归
在这个练习中,我们将学习如何使用优化器训练一个简单的线性回归模型。我们将首先假设一个线性方程 w*x + b 中的任意值(w 和 b)。通过优化器,我们将观察这些参数值是如何变化的,以便得到正确的参数值,从而映射输入值(x)与输出(y)之间的关系。使用优化后的参数值,我们将预测一些给定输入值(x)的输出(y)。完成此练习后,我们将看到,由优化参数预测的线性输出与实际输出值非常接近。按照以下步骤完成此练习:
-
打开一个 Jupyter Notebook 并将其重命名为 Exercise 1.06。
-
导入
tensorflow,创建变量并将其初始化为 0。这里,我们假设这两个参数的值都为零:import tensorflow as tf w=tf.Variable(0.0) b=tf.Variable(0.0) -
定义一个线性回归模型的函数。我们之前学习了如何在 TensorFlow 中创建函数:
def regression(x): model=w*x+b return model -
准备数据,以特征(
x)和标签(y)的形式呈现:x=[1,2,3,4] y=[0,-1,-2,-3] -
定义
loss函数。在此案例中,loss是预测值与标签值之间差的绝对值:loss=lambda:abs(regression(x)-y) -
创建一个学习率为
.01的Adam优化器实例。学习率定义了优化器应以多快的速度改变假设的参数。我们将在后续章节中讨论学习率:optimizer=tf.optimizers.Adam(.01) -
通过运行优化器 1,000 次迭代来训练模型,以最小化损失:
for i in range(1000): optimizer.minimize(loss,[w,b]) -
打印训练好的
w和b参数的值:tf.print(w,b)输出将如下所示:
-1.00371706 0.999803364我们可以看到,
w和b参数的值已经从假设的原始值 0 发生了变化。这正是优化过程中的操作。更新后的参数值将用于预测Y的值。注意
优化过程是随机的(具有随机概率分布),因此你可能得到与这里打印的值不同的
w和b的值。 -
使用训练好的模型通过输入
x值来预测输出。模型预测的值与标签值(y)非常接近,这意味着模型已经训练得非常精确:tf.print(regression([1,2,3,4]))上述命令的输出将如下所示:
[-0.00391370058 -1.00763083 -2.01134801 -3.01506495]注意
要访问此特定部分的源代码,请参考
packt.live/3gSBs8b。你也可以在线运行这个示例,链接地址是
packt.live/2OaFs7C。你必须执行整个 Notebook 才能得到期望的结果。
在本次练习中,我们学习了如何使用优化器来训练一个简单的线性回归模型。在此过程中,我们看到初始假设的参数值如何更新,以获得真实的参数值。通过使用真实的参数值,我们能够得到接近实际值的预测。理解如何应用优化器将帮助你后续训练神经网络模型。
现在我们已经看过优化器的使用,让我们将学到的知识应用到下一个活动中,通过优化函数来解一个二次方程。
活动 1.01:使用优化器解二次方程
在本次活动中,你将使用优化器来解下面的二次方程:
图 1.28:一个二次方程
完成本活动所需的高层次步骤如下:
-
打开一个新的 Jupyter Notebook,并导入必要的包,就像我们在之前的练习中所做的那样。
-
初始化变量。请注意,在此示例中,
x是你需要初始化的变量。你可以将其初始化为 0。 -
使用
lambda函数构造loss函数。loss函数将是你要解的二次方程。 -
使用学习率为
.01的Adam优化器。 -
在不同的迭代中运行优化器并最小化损失。你可以从 1,000 次迭代开始,然后在随后的试验中增加迭代次数,直到获得你想要的结果。
-
打印优化后的
x值。
预期的输出如下:
4.99919891
请注意,虽然你的实际输出可能略有不同,但它应该接近于 5。
注意
本活动的详细步骤、解决方案和附加评论,请参见第 388 页。
总结
本章到此结束。让我们回顾一下我们迄今为止学到的内容。我们首先了解了 AI、机器学习和深度学习之间的关系。接着,我们通过分类一张图片实现了深度学习的演示,并利用 Google API 实现了文本到语音的转换。随后,我们简要介绍了不同的深度学习应用场景和类型,如 MLP、CNN、RNN 和 GANs。
在下一部分中,我们介绍了 TensorFlow 框架,并了解了其中的一些基本构件,如张量及其秩和形状。我们还使用 TensorFlow 实现了不同的线性代数操作,如矩阵乘法。在本章后半部分,我们执行了一些有用的操作,如 reshape 和 argmax。最后,我们介绍了优化器的概念,并使用优化器解决了数学表达式问题。
现在我们已经为深度学习打下了基础,并向你介绍了 TensorFlow 框架,接下来你将可以深入探索神经网络的迷人世界。在下一章中,你将接触到神经网络,接下来的章节将深入探讨更多深度学习的概念。我们希望你能享受这段迷人的旅程。
第二章:2. 神经网络
概述
本章从介绍生物神经元开始,看看人工神经网络如何受生物神经网络的启发。我们将研究一个简单的单层神经元(称为感知器)的结构和内部工作原理,并学习如何在 TensorFlow 中实现它。接着,我们将构建多层神经网络来解决更复杂的多类分类任务,并讨论设计神经网络时的实际考虑。随着我们构建深度神经网络,我们将转向 Keras,在 Python 中构建模块化且易于定制的神经网络模型。到本章结束时,你将能熟练地构建神经网络来解决复杂问题。
介绍
在上一章中,我们学习了如何在 TensorFlow 中实现基本的数学概念,如二次方程、线性代数和矩阵乘法。现在,我们已经掌握了基础知识,让我们深入了解人工神经网络(ANNs),它们是人工智能和深度学习的核心。
深度学习是机器学习的一个子集。在监督学习中,我们经常使用传统的机器学习技术,如支持向量机或基于树的模型,其中特征是由人工明确设计的。然而,在深度学习中,模型会在没有人工干预的情况下探索并识别标记数据集中的重要特征。人工神经网络(ANNs),受生物神经元的启发,具有分层表示,这有助于它们从微小的细节到复杂的细节逐步学习标签。以图像识别为例:在给定的图像中,ANN 能够轻松识别诸如明暗区域这样的基本细节,也能识别更复杂的结构,如形状。尽管神经网络技术在识别图像中的物体等任务中取得了巨大成功,但它们的工作原理是一个黑箱,因为特征是隐式学习的。深度学习技术已经证明在解决复杂问题(如语音/图像识别)方面非常强大,因此被广泛应用于行业,如构建自动驾驶汽车、Google Now 和许多其他应用。
现在我们已经了解了深度学习技术的重要性,我们将采取一种务实的逐步方法,结合理论和实际考虑来理解构建基于深度学习的解决方案。我们将从神经网络的最小组件——人工神经元(也称为感知器)开始,逐步增加复杂性,探索多层感知器(MLPs)以及更先进的模型,如递归神经网络(RNNs)和卷积神经网络(CNNs)。
神经网络与感知器的结构
神经元是人类神经系统的基本构建块,它在全身传递电信号。人脑由数十亿个相互连接的生物神经元组成,它们通过开启或关闭自己,不断发送微小的电二进制信号互相通信。神经网络的普遍含义是相互连接的神经元的网络。在当前的背景下,我们指的是人工神经网络(ANNs),它们实际上是基于生物神经网络的模型。人工智能这一术语源自于自然智能存在于人脑(或任何大脑)这一事实,而我们人类正在努力模拟这种自然智能。尽管人工神经网络受到生物神经元的启发,但一些先进的神经网络架构,如卷积神经网络(CNNs)和递归神经网络(RNNs),并没有真正模仿生物神经元的行为。然而,为了便于理解,我们将首先通过类比生物神经元和人工神经元(感知机)来开始。
生物神经元的简化版本在图 2.1中表示:
图 2.1:生物神经元
这是一个高度简化的表示。它有三个主要组件:
-
树突,接收输入信号
-
细胞体,信号在其中以某种形式进行处理
-
尾部状的轴突,通过它神经元将信号传递到下一个神经元
感知机也可以用类似的方式表示,尽管它不是一个物理实体,而是一个数学模型。图 2.2展示了人工神经元的高级表示:
图 2.2:人工神经元的表示
在人工神经元中,和生物神经元一样,有一个输入信号。中央节点将所有信号合并,如果信号超过某个阈值,它将触发输出信号。感知机的更详细表示在图 2.3中展示。接下来的章节将解释这个感知机的每个组件:
图 2.3:感知机的表示
感知机有以下组成部分:
-
输入层
-
权重
-
偏置
-
网络输入函数
-
激活函数
让我们通过考虑一个OR表格数据集,详细查看这些组件及其 TensorFlow 实现。
输入层
每个输入数据的示例都会通过输入层传递。参考图 2.3中的表示,根据输入示例的大小,节点的数量将从x1 到xm 不等。输入数据可以是结构化数据(如 CSV 文件)或非结构化数据,如图像。这些输入,x1 到xm,被称为特征(m表示特征的数量)。我们通过一个例子来说明这一点。
假设数据以如下表格形式呈现:
图 2.4:样本输入和输出数据——OR 表
在这里,神经元的输入是列 x1 和 x2,它们对应于一行。此时可能很难理解,但暂时可以接受这样一个事实:在训练过程中,数据是以迭代的方式,一次输入一行。我们将使用 TensorFlow Variable 类如下表示输入数据和真实标签(输出 y):
X = tf.Variable([[0.,0.],[0.,1.],\
[1.,0.],[1.,1.]], \
tf.float32)
y = tf.Variable([0, 1, 1, 1], tf.float32)
权重
权重与每个神经元相关联,输入特征决定了每个输入特征在计算下一个节点时应有的影响力。每个神经元将与所有输入特征连接。在这个例子中,由于有两个输入(x1 和 x2),且输入层与一个神经元连接,所以会有两个与之相关联的权重:w1 和 w2。权重是一个实数,可以是正数或负数,数学上表示为 Variable 类,如下所示:
number_of_features = x.shape[1]
number_of_units = 1
Weight = tf.Variable(tf.zeros([number_of_features, \
number_of_units]), \
tf.float32)
权重的维度如下:输入特征的数量 × 输出大小。
偏置
在图 2.3中,偏置由 b 表示,称为加性偏置。每个神经元都有一个偏置。当 x 为零时,也就是说没有来自自变量的信息输入时,输出应该仅为 b。像权重一样,偏置也是一个实数,网络必须学习偏置值才能得到正确的预测结果。
在 TensorFlow 中,偏置与输出大小相同,可以表示如下:
B = tf.Variable(tf.zeros([1, 1]), tf.float32)
净输入函数
净输入函数,也常被称为输入函数,可以描述为输入与其对应权重的乘积之和,再加上偏置。数学上表示如下:
图 2.5:数学形式的净输入函数
这里:
-
xi:输入数据——x1 到 xm
-
wi:权重——w1 到 wm
-
b:加性偏置
如你所见,这个公式涉及到输入及其相关的权重和偏置。可以以向量化的形式写出,我们可以使用矩阵乘法,这是我们在第一章《深度学习基础》中学过的内容。我们将在开始代码演示时看到这一点。由于所有变量都是数字,净输入函数的结果只是一个数字,一个实数。净输入函数可以通过 TensorFlow 的 matmul 功能轻松实现,如下所示:
z = tf.add(tf.matmul(X, W), B)
W 代表权重,X 代表输入,B 代表偏置。
激活函数(G)
网络输入函数(z)的输出作为输入传递给激活函数。激活函数将网络输入函数(z)的输出压缩到一个新的输出范围,具体取决于激活函数的选择。有各种激活函数,如 Sigmoid(逻辑函数)、ReLU 和 tanh。每个激活函数都有其优缺点。我们将在本章稍后深入探讨激活函数。现在,我们先从 Sigmoid 激活函数开始,也叫逻辑函数。使用 Sigmoid 激活函数时,线性输出z被压缩到一个新的输出范围(0,1)。激活函数在层与层之间提供了非线性,这使得神经网络能够逼近任何连续函数。
Sigmoid 函数的数学公式如下,其中G(z)是 Sigmoid 函数,右边的公式详细说明了关于z的导数:
图 2.6:Sigmoid 函数的数学形式
如你在图 2.7中所见,Sigmoid 函数是一个大致为 S 形的曲线,其值介于 0 和 1 之间,无论输入是什么:
图 2.7:Sigmoid 曲线
如果我们设定一个阈值(比如0.5),我们可以将其转换为二进制输出。任何大于或等于.5的输出被视为1,任何小于.5的值被视为0。
TensorFlow 提供了现成的激活函数,如 Sigmoid。可以如下在 TensorFlow 中实现 Sigmoid 函数:
output = tf.sigmoid(z)
现在我们已经看到了感知机的结构以及其在 TensorFlow 中的代码表示,让我们把所有组件结合起来,构建一个感知机。
TensorFlow 中的感知机
在 TensorFlow 中,可以通过定义一个简单的函数来实现感知机,如下所示:
def perceptron(X):
z = tf.add(tf.matmul(X, W), B)
output = tf.sigmoid(z)
return output
在一个非常高的层次上,我们可以看到输入数据通过网络输入函数。网络输入函数的输出会传递给激活函数,激活函数反过来给我们预测的输出。现在,让我们逐行看一下代码:
z = tf.add(tf.matmul(X, W), B)
网络输入函数的输出存储在z中。让我们通过进一步分解,将结果分为两部分来看,即tf.matmul中的矩阵乘法部分和tf.add中的加法部分。
假设我们将X和W的矩阵乘法结果存储在一个名为m的变量中:
m = tf.matmul(X, W)
现在,让我们考虑一下如何得到这个结果。例如,假设X是一个行矩阵,如[ X1 X2 ],而W是一个列矩阵,如下所示:
图 2.8:列矩阵
回想一下上一章提到的,tf.matmul会执行矩阵乘法。因此,结果是这样的:
m = x1*w1 + x2*w2
然后,我们将输出m与偏置B相加,如下所示:
z = tf.add(m, B)
请注意,我们在前一步所做的与简单地将两个变量 m 和 b 相加是一样的:
m + b
因此,最终的输出是:
z = x1*w1 + x2*w2 + b
z 将是净输入函数的输出。
现在,让我们考虑下一行:
output= tf.sigmoid(z)
正如我们之前学到的,tf.sigmoid 是 Sigmoid 函数的现成实现。前一行计算的净输入函数的输出(z)作为输入传递给 Sigmoid 函数。Sigmoid 函数的结果就是感知器的输出,范围在 0 到 1 之间。在训练过程中,稍后将在本章中解释,我们将数据批量地输入到这个函数中,函数将计算预测值。
练习 2.01:感知器实现
在本练习中,我们将为 OR 表格实现一个感知器。在 TensorFlow 中设置输入数据并冻结感知器的设计参数:
-
让我们导入必要的包,在我们的案例中是
tensorflow:import tensorflow as tf -
在 TensorFlow 中设置
OR表格数据的输入数据和标签:X = tf.Variable([[0.,0.],[0.,1.],\ [1.,0.],[1.,1.]], \ dtype=tf.float32) print(X)正如你在输出中看到的,我们将得到一个 4 × 2 的输入数据矩阵:
<tf.Variable 'Variable:0' shape=(4, 2) dtype=float32, numpy=array([[0., 0.], [0., 1.], [1., 0.], [1., 1.]], dtype=float32)> -
我们将在 TensorFlow 中设置实际的标签,并使用
reshape()函数将y向量重塑为一个 4 × 1 的矩阵:y = tf.Variable([0, 1, 1, 1], dtype=tf.float32) y = tf.reshape(y, [4,1]) print(y)输出是一个 4 × 1 的矩阵,如下所示:
tf.Tensor( [[0.] [1.] [1.] [1.]], shape=(4, 1), dtype=float32) -
现在让我们设计感知器的参数。
神经元数量(单位) = 1
特征数量(输入) = 2(示例数量 × 特征数量)
激活函数将是 Sigmoid 函数,因为我们正在进行二元分类:
NUM_FEATURES = X.shape[1] OUTPUT_SIZE = 1在上面的代码中,
X.shape[1]将等于2(因为索引是从零开始的,1指的是第二个索引,它的值是2)。 -
在 TensorFlow 中定义连接权重矩阵:
W = tf.Variable(tf.zeros([NUM_FEATURES, \ OUTPUT_SIZE]), \ dtype=tf.float32) print(W)权重矩阵本质上将是一个列矩阵,如下图所示。它将具有以下维度:特征数量(列数) × 输出大小:
](tos-cn-i-73owjymdk6/8486a0bd7700455aa11d5a5a035b3270)
<tf.Variable 'Variable:0' shape=(2, 1) dtype=float32, \ numpy=array([[0.], [0.]], dtype=float32)> -
现在创建偏置的变量:
B = tf.Variable(tf.zeros([OUTPUT_SIZE, 1]), dtype=tf.float32) print(B)每个神经元只有一个偏置,因此在这种情况下,偏置只是一个单元素数组中的一个数字。然而,如果我们有一个包含 10 个神经元的层,那么它将是一个包含 10 个数字的数组——每个神经元对应一个。
这将导致一个零行矩阵,包含一个单一元素,如下所示:
<tf.Variable 'Variable:0' shape=(1, 1) dtype=float32, numpy=array([[0.]], dtype=float32)> -
现在我们已经有了权重和偏置,下一步是执行计算,得到净输入函数,将其输入到激活函数中,然后得到最终输出。让我们定义一个名为
perceptron的函数来获取输出:def perceptron(X): z = tf.add(tf.matmul(X, W), B) output = tf.sigmoid(z) return output print(perceptron(X))输出将是一个 4 × 1 的数组,包含我们感知器的预测结果:
tf.Tensor( [[0.5] [0.5] [0.5] [0.5]], shape=(4, 1), dtype=float32)如我们所见,预测结果并不十分准确。我们将在接下来的章节中学习如何改进结果。
注意
要访问该特定部分的源代码,请参考
packt.live/3feF7MO。你也可以在
packt.live/2CkMiEE上运行这个示例。你必须执行整个 Notebook 才能获得预期的结果。
在这个练习中,我们实现了一个感知机,它是单个人工神经元的数学实现。请记住,这只是模型的实现,我们还没有进行任何训练。在下一节中,我们将看到如何训练感知机。
训练感知机
要训练一个感知机,我们需要以下组件:
-
数据表示
-
层
-
神经网络表示
-
损失函数
-
优化器
-
训练循环
在前一节中,我们讲解了大部分前面的组件:perceptron(),它使用线性层和 sigmoid 层来执行预测。我们在前一节中使用输入数据和初始权重与偏差所做的工作被称为前向传播。实际的神经网络训练涉及两个阶段:前向传播和反向传播。我们将在接下来的几步中详细探讨它们。让我们从更高的层次来看训练过程:
-
神经网络遍历所有训练样本的训练迭代被称为一个 Epoch。这是需要调整的超参数之一,以便训练神经网络。
-
在每一轮传递中,神经网络都会进行前向传播,其中数据从输入层传输到输出层。正如在练习 2.01,感知机实现中所看到的,输入被馈送到感知机。输入数据通过网络输入函数和激活函数,生成预测输出。预测输出与标签或真实值进行比较,计算误差或损失。
-
为了让神经网络学习(即调整权重和偏差以做出正确预测),需要有一个损失函数,它将计算实际标签和预测标签之间的误差。
-
为了最小化神经网络中的误差,训练循环需要一个优化器,它将基于损失函数来最小化损失。
-
一旦计算出误差,神经网络就会查看网络中哪些节点对误差产生了影响,以及影响的程度。这对于在下一轮训练中提高预测效果至关重要。这个向后传播误差的方式被称为反向传播(backpropagation)。反向传播利用微积分中的链式法则,以反向顺序传播误差(误差梯度),直到达到输入层。在通过网络反向传播误差时,它使用梯度下降法根据之前计算的误差梯度对网络中的权重和偏差进行微调。
这个循环会持续进行,直到损失最小化。
让我们在 TensorFlow 中实现我们讨论过的理论。回顾一下 练习 2.01,感知机实现,在该练习中,我们创建的感知机只进行了一个前向传播。我们得到了以下预测结果,并且发现我们的感知机没有学到任何东西:
tf.Tensor(
[[0.5]
[0.5]
[0.5]
[0.5]], shape=(4, 1), dtype=float32)
为了让我们的感知机学习,我们需要一些额外的组件,例如训练循环、损失函数和优化器。让我们看看如何在 TensorFlow 中实现这些组件。
TensorFlow 中的感知机训练过程
在下一个练习中,当我们训练模型时,我们将使用 随机梯度下降(SGD)优化器来最小化损失。TensorFlow 提供了一些更高级的优化器。我们将在后续部分讨论它们的优缺点。以下代码将使用 TensorFlow 实例化一个随机梯度下降优化器:
learning_rate = 0.01
optimizer = tf.optimizers.SGD(learning_rate)
perceptron 函数负责前向传播。对于误差的反向传播,我们使用了一个优化器。Tf.optimizers.SGD 创建了一个优化器实例。SGD 会在每个输入数据的示例上更新网络的参数——权重和偏置。我们将在本章后续部分更详细地讨论梯度下降优化器的工作原理。我们还会讨论 0.01 参数的意义,该参数被称为学习率。学习率是 SGD 为了达到损失函数的全局最优解而采取的步伐的大小。学习率是另一个超参数,需要调节以训练神经网络。
以下代码可用于定义训练周期、训练循环和损失函数:
no_of_epochs = 1000
for n in range(no_of_epochs):
loss = lambda:abs(tf.reduce_mean(tf.nn.\
sigmoid_cross_entropy_with_logits\
(labels=y,logits=perceptron(X))))
optimizer.minimize(loss, [W, B])
在训练循环中,损失是通过损失函数计算的,损失函数被定义为一个 lambda 函数。
tf.nn.sigmoid_cross_entropy_with_logits 函数计算每个观测值的损失值。它接受两个参数:Labels = y 和 logit = perceptron(x)。
perceptron(X) 返回预测值,这是输入 x 的前向传播结果。这个结果与存储在 y 中的相应标签值进行比较。使用 Tf.reduce_mean 计算平均值,并取其大小。使用 abs 函数忽略符号。Optimizer.minimize 会根据损失值调整权重和偏置,这是误差反向传播的一部分。
使用新的权重和偏置值再次执行前向传播。这个前向和反向过程会持续进行,直到我们定义的迭代次数结束。
在反向传播过程中,只有当损失小于上一个周期的损失时,权重和偏置才会被更新。否则,权重和偏置保持不变。通过这种方式,优化器确保尽管它会执行所需的迭代次数,但只会存储那些损失最小的 w 和 b 值。
我们将训练的轮数设置为 1,000 次迭代。设置训练轮数没有固定的经验法则,因为轮数是一个超参数。那么,我们如何知道训练是否成功呢?
当我们看到权重和偏置的值发生变化时,我们可以得出结论,训练已经发生。假设我们使用了练习 2.01中的OR数据进行训练,并应用了感知机实现,我们会看到权重大致等于以下值:
[[0.412449151]
[0.412449151]]
偏置值可能是这样的:
0.236065879
当网络已经学习,即权重和偏置已经更新时,我们可以使用scikit-learn包中的accuracy_score来查看它是否做出了准确的预测。我们可以通过如下方式来测量预测的准确性:
from sklearn.metrics import accuracy_score
print(accuracy_score(y, ypred))
在这里,accuracy_score接收两个参数——标签值(y)和预测值(ypred)——并计算准确率。假设结果是1.0,这意味着感知机的准确率为 100%。
在下一个练习中,我们将训练感知机来执行二分类任务。
练习 2.02:感知机作为二分类器
在上一节中,我们学习了如何训练感知机。在本练习中,我们将训练感知机来近似一个稍微复杂一些的函数。我们将使用随机生成的外部数据,数据有两个类别:类别0和类别1。我们训练后的感知机应该能够根据类别来分类这些随机数:
注意
数据存储在名为data.csv的 CSV 文件中。你可以通过访问packt.live/2BVtxIf从 GitHub 下载该文件。
-
导入所需的库:
import tensorflow as tf import pandas as pd from sklearn.metrics import confusion_matrix from sklearn.metrics import accuracy_score import matplotlib.pyplot as plt %matplotlib inline除了
tensorflow,我们还需要pandas来从 CSV 文件读取数据,confusion_matrix和accuracy_score来衡量训练后感知机的准确性,以及matplotlib来可视化数据。 -
从
data.csv文件中读取数据。该文件应与运行此练习代码的 Jupyter Notebook 文件在同一路径下。否则,在执行代码之前你需要更改路径:df = pd.read_csv('data.csv') -
检查数据:
df.head()输出将如下所示:
图 2.10:DataFrame 的内容
如你所见,数据有三列。
x1和x2是特征,而label列包含每个观测的标签0或1。查看这种数据的最佳方式是通过散点图。 -
使用
matplotlib绘制图表来可视化数据:plt.scatter(df[df['label'] == 0]['x1'], \ df[df['label'] == 0]['x2'], \ marker='*') plt.scatter(df[df['label'] == 1]['x1'], \ df[df['label'] == 1]['x2'], marker='<')输出将如下所示:
图 2.11:外部数据的散点图
这显示了数据的两个不同类别,通过两种不同的形状来表示。标签为
0的数据用星号表示,而标签为1的数据用三角形表示。 -
准备数据。这一步骤不仅仅是神经网络特有的,你在常规机器学习中也一定见过。在将数据提交给模型进行训练之前,你需要将数据分割为特征和标签:
X_input = df[['x1','x2']].values y_label = df[['label']].valuesx_input包含特征x1和x2。末尾的值将其转换为矩阵格式,这是创建张量时所期望的输入格式。y_label包含矩阵格式的标签。 -
创建 TensorFlow 变量用于特征和标签,并将它们转换为
float类型:x = tf.Variable(X_input, dtype=tf.float32) y = tf.Variable(y_label, dtype=tf.float32) -
剩下的代码是用来训练感知器的,我们在练习 2.01,感知器实现中看过:
Exercise2.02.ipynb Number_of_features = 2 Number_of_units = 1 learning_rate = 0.01 # weights and bias weight = tf.Variable(tf.zeros([Number_of_features, \ Number_of_units])) bias = tf.Variable(tf.zeros([Number_of_units])) #optimizer optimizer = tf.optimizers.SGD(learning_rate) def perceptron(x): z = tf.add(tf.matmul(x,weight),bias) output = tf.sigmoid(z) return output The complete code for this step can be found at https://packt.live/3gJ73bY.注意
上述代码片段中的
#符号表示代码注释。注释被添加到代码中以帮助解释特定的逻辑部分。 -
显示
weight和bias的值,以展示感知器已经被训练过:tf.print(weight, bias)输出结果如下:
[[-0.844034135] [0.673354745]] [0.0593947917] -
将输入数据传递进去,检查感知器是否正确分类:
ypred = perceptron(x) -
对输出结果进行四舍五入,转换成二进制格式:
ypred = tf.round(ypred) -
使用
accuracy_score方法来衡量准确性,正如我们在之前的练习中所做的那样:acc = accuracy_score(y.numpy(), ypred.numpy()) print(acc)输出结果如下:
1.0该感知器给出了 100%的准确率。
-
混淆矩阵帮助评估模型的性能。我们将使用
scikit-learn包来绘制混淆矩阵。cnf_matrix = confusion_matrix(y.numpy(), \ ypred.numpy()) print(cnf_matrix)输出结果将如下所示:
[[12 0] [ 0 9]]所有的数字都位于对角线上,即,12 个值对应于类别 0,9 个值对应于类别 1,这些都被我们训练好的感知器正确分类(该感知器已达到 100%的准确率)。
注意
要查看这个具体部分的源代码,请参考
packt.live/3gJ73bY。你也可以在网上运行这个示例,访问
packt.live/2DhelFw。你必须执行整个 Notebook 才能得到预期的结果。
在本练习中,我们将感知器训练成了一个二分类器,并且表现得相当不错。在下一个练习中,我们将看到如何创建一个多分类器。
多分类器
一个可以处理两类的分类器被称为二分类器,就像我们在之前的练习中看到的那样。一个可以处理多于两类的分类器被称为多分类器。我们无法使用单一神经元来构建多分类器。现在我们从一个神经元转变为一个包含多个神经元的层,这对于多分类器是必需的。
一层多个神经元可以被训练成一个多分类器。这里详细列出了一些关键点。你需要的神经元数量等于类别的数量;也就是说,对于一个 3 类的分类器,你需要 3 个神经元;对于一个 10 类的分类器,你需要 10 个神经元,依此类推。
如我们在二分类中看到的,我们使用 sigmoid(逻辑层)来获取 0 到 1 范围内的预测。在多类分类中,我们使用一种特殊类型的激活函数,称为Softmax激活函数,以获得每个类别的概率,总和为 1。使用 sigmoid 函数进行多类分类时,概率不一定加起来为 1,因此更倾向使用 Softmax。
在实现多类分类器之前,让我们先探索 Softmax 激活函数。
Softmax 激活函数
Softmax 函数也被称为归一化指数函数。正如归一化一词所暗示的,Softmax 函数将输入归一化为一个总和为 1 的概率分布。从数学角度来看,它表示为:
图 2.12:Softmax 函数的数学形式
为了理解 Softmax 的作用,让我们使用 TensorFlow 内置的softmax函数并查看输出。
所以,对于以下代码:
values = tf.Variable([3,1,7,2,4,5], dtype=tf.float32)
output = tf.nn.softmax(values)
tf.print(output)
输出结果将是:
[0.0151037546 0.00204407098 0.824637055
0.00555636082 0.0410562605 0.111602485]
如您所见,输出中values输入被映射到一个概率分布,且总和为 1。注意,7(原始输入值中的最大值)获得了最高的权重,0.824637055。这正是 Softmax 函数的主要用途:专注于最大值,并抑制低于最大值的值。此外,如果我们对输出求和,结果将接近 1。
详细说明该示例,假设我们想构建一个包含 3 个类别的多类分类器。我们将需要连接到 Softmax 激活函数的 3 个神经元:
图 2.13:在多类分类设置中使用的 Softmax 激活函数
如图 2.13所示,x1、x2 和x3 是输入特征,它们经过每个神经元的网络输入函数,这些神经元具有与之相关的权重和偏置(Wi,j 和 bi)。最后,神经元的输出被送入通用的 Softmax 激活函数,而不是单独的 sigmoid 函数。Softmax 激活函数输出 3 个类别的概率:P1、P2和P3。由于 Softmax 层的存在,这三个概率的总和将为 1。
正如我们在前一部分看到的,Softmax 突出最大值并抑制其余的值。假设一个神经网络被训练来将输入分类为三个类别,对于给定的输入集,输出为类别 2;那么它会说P2具有最高值,因为它经过了 Softmax 层。如下面的图所示,P2具有最高值,这意味着预测是正确的:
图 2.14:概率 P2 最大
相关概念是独热编码。由于我们有三个不同的类别,class1、class2和class3,我们需要将类别标签编码为便于操作的格式;因此,应用独热编码后,我们会看到如下输出:
图 2.15:三个类别的独热编码数据
这样可以使结果快速且容易解释。在这种情况下,值最高的输出被设置为 1,所有其他值设置为 0。上述例子的独热编码输出将如下所示:
图 2.16:独热编码的输出概率
训练数据的标签也需要进行独热编码。如果它们格式不同,则需要在训练模型之前将其转换为独热编码格式。让我们进行一次关于独热编码的多类分类练习。
练习 2.03:使用感知机进行多类分类
为了执行多类分类,我们将使用鸢尾花数据集(archive.ics.uci.edu/ml/datasets/Iris),该数据集包含 3 个类别,每个类别有 50 个实例,每个类别代表一种鸢尾花。我们将使用一个包含三个神经元的单层,采用 Softmax 激活函数:
注意
你可以通过这个链接从 GitHub 下载数据集:packt.live/3ekiBBf。
-
导入所需的库:
import tensorflow as tf import pandas as pd from sklearn.metrics import confusion_matrix from sklearn.metrics import accuracy_score import matplotlib.pyplot as plt %matplotlib inline from pandas import get_dummies你应该熟悉所有这些导入,因为它们在前一个练习中已使用过,除了
get_dummies。此函数将给定的标签数据转换为相应的独热编码格式。 -
加载
iris.csv数据:df = pd.read_csv('iris.csv') -
让我们查看数据的前五行:
df.head()输出结果如下:
图 2.17:DataFrame 的内容
-
使用散点图可视化数据:
plt.scatter(df[df['species'] == 0]['sepallength'],\ df[df['species'] == 0]['sepalwidth'], marker='*') plt.scatter(df[df['species'] == 1]['sepallength'],\ df[df['species'] == 1]['sepalwidth'], marker='<') plt.scatter(df[df['species'] == 2]['sepallength'], \ df[df['species'] == 2]['sepalwidth'], marker='o')结果图如下所示。x轴表示花萼长度,y轴表示花萼宽度。图中的形状表示三种鸢尾花的品种,setosa(星形)、versicolor(三角形)和 virginica(圆形):
图 2.18:鸢尾花数据散点图
如可视化所示,共有三个类别,用不同的形状表示。
-
将特征和标签分开:
x = df[['petallength', 'petalwidth', \ 'sepallength', 'sepalwidth']].values y = df['species'].valuesvalues将把特征转换为矩阵格式。 -
通过对类别进行独热编码来准备数据:
y = get_dummies(y) y = y.valuesget_dummies(y)将把标签转换为独热编码格式。 -
创建一个变量来加载特征,并将其类型转换为
float32:x = tf.Variable(x, dtype=tf.float32) -
使用三个神经元实现
感知机层:Number_of_features = 4 Number_of_units = 3 # weights and bias weight = tf.Variable(tf.zeros([Number_of_features, \ Number_of_units])) bias = tf.Variable(tf.zeros([Number_of_units])) def perceptron(x): z = tf.add(tf.matmul(x, weight), bias) output = tf.nn.softmax(z) return output这段代码看起来与单一感知机实现非常相似。只是将
Number_of_units参数设置为3。因此,权重矩阵将是 4 x 3,偏置矩阵将是 1 x 3。另一个变化是在激活函数中:
Output=tf.nn.softmax(x)我们使用的是
softmax而不是sigmoid。 -
创建一个
optimizer实例。我们将使用Adam优化器。在这一点上,你可以将Adam视为一种改进版的梯度下降法,它收敛速度更快。我们将在本章稍后详细讲解:optimizer = tf.optimizers.Adam(.01) -
定义训练函数:
def train(i): for n in range(i): loss=lambda: abs(tf.reduce_mean\ (tf.nn.softmax_cross_entropy_with_logits(\ labels=y, logits=perceptron(x)))) optimizer.minimize(loss, [weight, bias])再次说明,代码看起来与单神经元实现非常相似,唯一的不同是损失函数。我们使用的是
softmax_cross_entropy_with_logits,而不是sigmoid_cross_entropy_with_logits。 -
运行训练
1000次迭代:train(1000) -
打印权重值以查看它们是否发生了变化。这也是我们感知器在学习的一个标志:
tf.print(weight)输出显示我们感知器学习到的权重:
[[0.684310317 0.895633 -1.0132345] [2.6424644 -1.13437736 -3.20665336] [-2.96634197 -0.129377216 3.2572844] [-2.97383809 -3.13501668 3.2313652]] -
为了测试准确率,我们将特征输入以预测输出,然后使用
accuracy_score计算准确率,就像在前面的练习中一样:ypred=perceptron(x) ypred=tf.round(ypred) accuracy_score(y, ypred)输出为:
0.98它的准确率达到了 98%,非常不错。
注意
若要访问此特定部分的源代码,请参考
packt.live/2Dhes3U。你也可以在线运行此示例,网址是
packt.live/3iJJKkm。你必须执行整个 Notebook 才能得到期望的结果。
在这个练习中,我们使用感知器进行了多类分类。接下来,我们将进行一个更复杂、更有趣的手写数字识别数据集的案例研究。
MNIST 案例研究
现在,我们已经了解了如何训练单个神经元和单层神经网络,接下来让我们看看更现实的数据。MNIST 是一个著名的案例研究。在下一个练习中,我们将创建一个 10 类分类器来分类 MNIST 数据集。不过,在那之前,你应该对 MNIST 数据集有一个充分的了解。
修改版国家标准与技术研究院(MNIST)是指由 Yann LeCun 领导的团队在 NIST 使用的修改数据集。这个项目的目标是通过神经网络进行手写数字识别。
在开始编写代码之前,我们需要了解数据集。MNIST 数据集已经集成到 TensorFlow 库中。它包含了 70,000 张手写数字 0 到 9 的图像:
图 2.19:手写数字
当我们提到图像时,你可能会认为它们是 JPEG 文件,但实际上它们不是。它们是以像素值的形式存储的。从计算机的角度来看,图像就是一堆数字。这些数字是从 0 到 255 之间的像素值。这些图像的维度是 28 x 28。图像是以 28 x 28 矩阵的形式存储的,每个单元包含从 0 到 255 之间的实数。这些是灰度图像(通常称为黑白图像)。0 表示白色,1 表示完全黑色,中间的值表示不同深浅的灰色。MNIST 数据集分为 60,000 张训练图像和 10,000 张测试图像。
每张图片都有一个标签,标签范围从 0 到 9。下一次练习中,我们将构建一个 10 类分类器来分类手写的 MNIST 图片。
练习 2.04:分类手写数字
在本练习中,我们将构建一个由 10 个神经元组成的单层 10 类分类器,采用 Softmax 激活函数。它将有一个 784 像素的输入层:
-
导入所需的库和包,就像我们在前面的练习中做的那样:
import tensorflow as tf import pandas as pd from sklearn.metrics import accuracy_score import matplotlib.pyplot as plt %matplotlib inline from pandas import get_dummies -
创建 MNIST 数据集的实例:
mnist = tf.keras.datasets.mnist -
加载 MNIST 数据集的
train和test数据:(train_features, train_labels), (test_features, test_labels) = \ mnist.load_data() -
对数据进行归一化:
train_features, test_features = train_features / 255.0, \ test_features / 255.0 -
将二维图像展平为行矩阵。因此,一个 28 × 28 像素的图像将被展平为
784,使用reshape函数:x = tf.reshape(train_features,[60000, 784]) -
创建一个
Variable,并将其类型转换为float32:x = tf.Variable(x) x = tf.cast(x, tf.float32) -
创建标签的独热编码并将其转换为矩阵:
y_hot = get_dummies(train_labels) y = y_hot.values -
创建一个包含
10个神经元的单层神经网络,并训练1000次:Exercise2.04.ipynb #defining the parameters Number_of_features = 784 Number_of_units = 10 # weights and bias weight = tf.Variable(tf.zeros([Number_of_features, \ Number_of_units])) bias = tf.Variable(tf.zeros([Number_of_units])) The complete code for this step can be accessed from https://packt.live/3efd7Yh. -
准备测试数据以评估准确率:
# Prepare the test data to measure the accuracy. test = tf.reshape(test_features, [10000, 784]) test = tf.Variable(test) test = tf.cast(test, tf.float32) test_hot = get_dummies(test_labels) test_matrix = test_hot.values -
通过将测试数据传入网络来进行预测:
ypred = perceptron(test) ypred = tf.round(ypred) -
计算准确率:
accuracy_score(test_hot, ypred)预测的准确率是:
0.9304注意
若要访问此部分的源代码,请参考
packt.live/3efd7Yh。你也可以在线运行此示例,地址是
packt.live/2Oc83ZW。你必须执行整个 Notebook 才能获得期望的结果。
在这个练习中,我们展示了如何创建一个单层多神经元神经网络,并将其训练为一个多类分类器。
下一步是构建一个多层神经网络。然而,在此之前,我们必须了解 Keras API,因为我们使用 Keras 来构建密集神经网络。
Keras 作为高级 API
在 TensorFlow 1.0 中,有多个 API,比如 Estimator、Contrib 和 layers。而在 TensorFlow 2.0 中,Keras 与 TensorFlow 紧密集成,提供了一个用户友好的、高级的 API,具有模块化、可组合且易于扩展的特性,可以用来构建和训练深度学习模型。这也使得开发神经网络代码变得更加简单。让我们来看它是如何工作的。
练习 2.05:使用 Keras 进行二分类
在这个练习中,我们将使用 Keras API 实现一个非常简单的二分类器,只有一个神经元。我们将使用与练习 2.02、感知机作为二分类器中相同的data.csv文件:
注意
数据集可以通过访问以下 GitHub 链接进行下载:packt.live/2BVtxIf。
-
导入所需的库:
import tensorflow as tf import pandas as pd import matplotlib.pyplot as plt %matplotlib inline # Import Keras libraries from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense在代码中,
Sequential是我们将使用的 Keras 模型类型,因为它非常容易向其中添加层。Dense是将要添加的层类型。这些是常规的神经网络层,而不是稍后将使用的卷积层或池化层。 -
导入数据:
df = pd.read_csv('data.csv') -
检查数据:
df.head()以下是输出结果:
图 2.20:DataFrame 的内容
-
使用散点图可视化数据:
plt.scatter(df[df['label'] == 0]['x1'], \ df[df['label'] == 0]['x2'], marker='*') plt.scatter(df[df['label'] == 1]['x1'], \ df[df['label'] == 1]['x2'], marker='<')生成的图形如下,x轴表示
x1值,y 轴表示x2值:图 2.21:数据的散点图
-
通过分离特征和标签并设置
tf变量来准备数据:x_input = df[['x1','x2']].values y_label = df[['label']].values -
创建一个神经网络模型,由一个神经元和一个 sigmoid 激活函数组成:
model = Sequential() model.add(Dense(units=1, input_dim=2, activation='sigmoid'))mymodel.add(Dense())中的参数如下:units是该层神经元的数量;input_dim是特征的数量,在此案例中为2;activation是sigmoid。 -
一旦模型创建完成,我们使用
compile方法传入训练所需的额外参数,如优化器类型、损失函数等:model.compile(optimizer='adam', \ loss='binary_crossentropy',\ metrics=['accuracy'])在这个案例中,我们使用了
adam优化器,这是梯度下降优化器的增强版,损失函数是binary_crossentropy,因为这是一个二分类器。metrics参数几乎总是设置为['accuracy'],用于显示如训练轮数、训练损失、训练准确度、测试损失和测试准确度等信息。 -
现在模型已准备好进行训练。然而,使用
summary函数检查模型配置是个好主意:model.summary()输出将如下所示:
图 2.22:顺序模型摘要
-
通过调用
fit()方法来训练模型:model.fit(x_input, y_label, epochs=1000)它接受特征和标签作为数据参数,并包含训练的轮数,在此案例中为
1000。模型将开始训练,并会持续显示状态,如下所示:图 2.23:使用 Keras 的模型训练日志
-
我们将使用 Keras 的
evaluate功能来评估模型:model.evaluate(x_input, y_label)输出结果如下:
21/21 [==============================] - 0s 611us/sample - loss: 0.2442 - accuracy: 1.0000 [0.24421504139900208, 1.0]如你所见,我们的 Keras 模型训练得非常好,准确率达到了 100%。
注意
要访问此特定章节的源代码,请参考
packt.live/2ZVV1VY。你也可以在线运行此示例,网址是
packt.live/38CzhTc。你必须执行整个笔记本才能得到期望的结果。
在本练习中,我们学习了如何使用 Keras 构建感知机。正如你所见,Keras 使代码更加模块化、更具可读性,而且参数调整也更为简便。在下一节中,我们将学习如何使用 Keras 构建多层或深度神经网络。
多层神经网络或深度神经网络
在前面的示例中,我们开发了一个单层神经网络,通常称为浅层神经网络。其示意图如下所示:
](tos-cn-i-73owjymdk6/9bddaede5dde439db999e2bc555187fb)
图 2.24:浅层神经网络
一层神经元不足以解决更复杂的问题,如人脸识别或物体检测。你需要堆叠多个层,这通常被称为创建深度神经网络。其示意图如下所示:
](tos-cn-i-73owjymdk6/5d73fde65a3648beac4b8dcd98372c34)
图 2.25:深度神经网络
在我们跳入代码之前,让我们试着理解一下这个过程是如何工作的。输入数据被馈送到第一层的神经元。需要注意的是,每个输入都会馈送到第一层的每个神经元,并且每个神经元都有一个输出。第一层每个神经元的输出会被馈送到第二层的每个神经元,第二层每个神经元的输出会被馈送到第三层的每个神经元,依此类推。
因此,这种网络也被称为密集神经网络或全连接神经网络。还有其他类型的神经网络,其工作原理不同,比如卷积神经网络(CNN),但这些内容我们将在下一章讨论。每一层中神经元的数量没有固定规则,通常通过试错法来确定,这个过程叫做超参数调优(我们将在本章后面学习)。然而,在最后一层神经元的数量上,是有一些限制的。最后一层的配置如下所示:
](tos-cn-i-73owjymdk6/5d73fde65a3648beac4b8dcd98372c34)
图 2.26:最后一层配置
ReLU 激活函数
在我们实现深度神经网络的代码之前,最后需要了解一下 ReLU 激活函数。这是多层神经网络中最常用的激活函数之一。
ReLU 是 **Rectified Linear Unit(修正线性单元)**的缩写。ReLU 函数的输出总是一个非负值,且大于或等于 0:
](tos-cn-i-73owjymdk6/68277ba6d030414fadc54d31dff818ef)
图 2.27:ReLU 激活函数
ReLU 的数学表达式是:
图 2.28:ReLU 激活函数
ReLU 收敛速度比 sigmoid 激活函数快得多,因此它是目前最广泛使用的激活函数。几乎所有的深度神经网络都使用 ReLU。它被应用于除最后一层外的所有层,最后一层则使用 sigmoid 或 Softmax。
ReLU 激活函数是 TensorFlow 内置提供的。为了了解它是如何实现的,我们给 ReLU 函数输入一些示例值,看看输出:
values = tf.Variable([1.0, -2., 0., 0.3, -1.5], dtype=tf.float32)
output = tf.nn.relu(values)
tf.print(output)
输出如下:
[1 0 0 0.3 0]
如你所见,所有正值都被保留,负值被压制为零。接下来我们将在下一个练习中使用这个 ReLU 激活函数来完成多层二分类任务。
练习 2.06:多层二分类器
在本次练习中,我们将使用在练习 2.02中使用的data.csv文件来实现一个多层二分类器,感知机作为二分类器。
我们将构建一个深度神经网络二分类器,配置如下:输入层有 2 个节点,包含 2 个隐藏层,第一个层有 50 个神经元,第二个层有 20 个神经元,最后是一个神经元,用于进行最终的二分类预测:
注意
数据集可以通过以下链接从 GitHub 下载:packt.live/2BVtxIf .
-
导入所需的库和包:
import tensorflow as tf import pandas as pd import matplotlib.pyplot as plt %matplotlib inline ##Import Keras libraries from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense -
导入并检查数据:
df = pd.read_csv('data.csv') df.head()输出如下:
图 2.29:数据的前五行
-
使用散点图可视化数据:
plt.scatter(df[df['label'] == 0]['x1'], \ df[df['label'] == 0]['x2'], marker='*') plt.scatter(df[df['label'] == 1]['x1'], \ df[df['label'] == 1]['x2'], marker='<')结果输出如下,x 轴显示
x1值,y 轴显示x2值:图 2.30:给定数据的散点图
-
通过分离特征和标签并设置
tf变量来准备数据:x_input = df[['x1','x2']].values y_label = df[['label']].values -
构建
Sequential模型:model = Sequential() model.add(Dense(units = 50,input_dim=2, activation = 'relu')) model.add(Dense(units = 20 , activation = 'relu')) model.add(Dense(units = 1,input_dim=2, activation = 'sigmoid'))以下是几个需要考虑的要点。我们提供了第一层的输入细节,然后对所有中间层使用 ReLU 激活函数,如前所述。此外,最后一层只有一个神经元,并且使用 sigmoid 激活函数来进行二分类。
-
使用
compile方法提供训练参数:model.compile(optimizer='adam', \ loss='binary_crossentropy', metrics=['accuracy']) -
使用
summary函数检查model配置:model.summary()输出将如下所示:
图 2.31:使用 Keras 深度神经网络模型总结
在模型总结中,我们可以看到,总共有
1191个参数——权重和偏置——需要在隐藏层到输出层之间进行学习。 -
通过调用
fit()方法训练模型:model.fit(x_input, y_label, epochs=50)请注意,在这种情况下,模型在
50个 epoch 内达到了 100% 的准确率,而单层模型大约需要 1,000 个 epoch:图 2.32:多层模型训练日志
-
让我们评估模型的性能:
model.evaluate(x_input, y_label)输出如下:
21/21 [==============================] - 0s 6ms/sample - loss: 0.1038 - accuracy: 1.0000 [0.1037961095571518, 1.0]我们的模型现在已经训练完成,并且展示了 100% 的准确率。
注意
要访问此特定部分的源代码,请参阅
packt.live/2ZUkM94。你也可以在
packt.live/3iKsD1W在线运行这个示例。你必须执行整个 Notebook 才能获得期望的结果。
在这个练习中,我们学习了如何使用 Keras 构建一个多层神经网络。这是一个二分类器。在下一个练习中,我们将为 MNIST 数据集构建一个深度神经网络,用于多类分类器。
练习 2.07:使用 Keras 在 MNIST 上实现深度神经网络
在这个练习中,我们将通过实现一个深度神经网络(多层)来对 MNIST 数据集进行多类分类,其中输入层包含 28 × 28 的像素图像,展平成 784 个输入节点,后面有 2 个隐藏层,第一个隐藏层有 50 个神经元,第二个隐藏层有 20 个神经元。最后,会有一个 Softmax 层,包含 10 个神经元,因为我们要将手写数字分类为 10 个类别:
-
导入所需的库和包:
import tensorflow as tf import pandas as pd import matplotlib.pyplot as plt %matplotlib inline # Import Keras libraries from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense from tensorflow.keras.layers import Flatten -
加载 MNIST 数据:
mnist = tf.keras.datasets.mnist (train_features,train_labels), (test_features,test_labels) = \ mnist.load_data()train_features包含的是 28 x 28 像素值形式的训练图像。train_labels包含训练标签。类似地,test_features包含 28 x 28 像素值形式的测试图像,test_labels包含测试标签。 -
对数据进行归一化:
train_features, test_features = train_features / 255.0, \ test_features / 255.0图像的像素值范围为 0-255。我们需要通过将它们除以 255 来对这些值进行归一化,使其范围从 0 到 1。
-
构建
sequential模型:model = Sequential() model.add(Flatten(input_shape=(28,28))) model.add(Dense(units = 50, activation = 'relu')) model.add(Dense(units = 20 , activation = 'relu')) model.add(Dense(units = 10, activation = 'softmax'))有几点需要注意。首先,在这个案例中,第一层实际上并不是一层神经元,而是一个
Flatten函数。它将 28 x 28 的图像展平成一个包含784的一维数组,这个数组会输入到第一个隐藏层,隐藏层有50个神经元。最后一层有10个神经元,对应 10 个类别,并使用softmax激活函数。 -
使用
compile方法提供训练参数:model.compile(optimizer = 'adam', \ loss = 'sparse_categorical_crossentropy', \ metrics = ['accuracy'])注意
这里使用的损失函数与二分类器不同。对于多分类器,使用以下损失函数:
sparse_categorical_crossentropy,当标签未经过 one-hot 编码时使用,如本例所示;以及categorical_crossentropy,当标签已进行 one-hot 编码时使用。 -
使用
summary函数检查模型配置:model.summary()输出如下:
](tos-cn-i-73owjymdk6/698217b8dc1f4fcf9da9592f89656812)
图 2.33:深度神经网络摘要
在模型摘要中,我们可以看到总共有 40,480 个参数——权重和偏差——需要在隐藏层到输出层之间进行学习。
-
通过调用
fit方法来训练模型:model.fit(train_features, train_labels, epochs=50)输出将如下所示:
](tos-cn-i-73owjymdk6/5b5a47042f4a4559b89c65eb6ee74a86)
图 2.34:深度神经网络训练日志
-
通过调用
evaluate()函数来测试模型:model.evaluate(test_features, test_labels)输出将是:
10000/10000 [==============================] - 1s 76us/sample - loss: 0.2072 - accuracy: 0.9718 [0.20719025060918111, 0.9718]现在模型已经训练并测试完成,在接下来的几个步骤中,我们将用一些随机选择的图像进行预测。
-
从测试数据集中加载一张随机图像。我们选择第 200 张图像:
loc = 200 test_image = test_features[loc] -
让我们使用以下命令查看图像的形状:
test_image.shape输出结果为:
(28,28)我们可以看到图像的形状是 28 x 28。然而,模型期望的是三维输入。我们需要相应地重塑图像。
-
使用以下代码来重塑图像:
test_image = test_image.reshape(1,28,28) -
让我们调用模型的
predict()方法,并将输出存储在一个名为result的变量中:result = model.predict(test_image) print(result)result以 10 个概率值的形式输出,如下所示:[[2.9072076e-28 2.1215850e-29 1.7854708e-21 1.0000000e+00 0.0000000e+00 1.2384960e-15 1.2660366e-34 1.7712217e-32 1.7461657e-08 9.6417470e-29]] -
最高值的位置就是预测结果。让我们使用在上一章中学到的
argmax函数来查找预测结果:result.argmax()在这个例子中,它是
3:3 -
为了检查预测是否正确,我们检查相应图像的标签:
test_labels[loc]再次,值是
3:3 -
我们也可以使用
pyplot可视化图像:plt.imshow(test_features[loc])输出将如下所示:
图 2.35:测试图像可视化
这表明预测是正确的。
注意
要访问该部分的源代码,请参考packt.live/2O5KRgd。
你也可以在packt.live/2O8JHR0上在线运行这个示例。你必须执行整个 Notebook 才能获得期望的结果。
在本练习中,我们使用 Keras 创建了一个多层多类神经网络模型,用于对 MNIST 数据进行分类。通过我们构建的模型,我们能够正确预测一个随机的手写数字。
探索神经网络的优化器和超参数
训练神经网络以获得良好的预测结果需要调整许多超参数,例如优化器、激活函数、隐藏层的数量、每层神经元的数量、训练轮次和学习率。让我们逐一讨论每一个超参数,并详细解释它们。
梯度下降优化器
在之前名为TensorFlow 中的感知机训练过程的部分中,我们简要提到了梯度下降优化器,但没有深入讨论其工作原理。现在是时候稍微详细了解一下梯度下降优化器了。我们将提供一个直观的解释,而不涉及数学细节。
梯度下降优化器的作用是最小化损失或误差。为了理解梯度下降是如何工作的,可以这样类比:想象一个人站在山顶,想要到达山脚。训练开始时,损失很大,就像山顶的高度。优化器的工作就像这个人从山顶走到山谷底部,或者说,走到山的最低点,而不是走到山的另一边。
记得我们在创建优化器时使用的学习率参数吗?它可以与人们下坡时采取的步伐大小进行比较。如果这些步伐很大,刚开始时是没问题的,因为这样可以更快地下坡,但一旦接近山谷底部,如果步伐过大,就会跨过山谷的另一边。然后,为了重新下到山谷底部,这个人会尝试回到原地,但又会再次跨越到另一边。结果就是在两边来回移动,始终无法到达山谷底部。
另一方面,如果一个人采取非常小的步伐(非常小的学习率),他们将永远无法到达山谷底部;换句话说,模型将永远无法收敛。因此,找到一个既不太小也不太大的学习率非常重要。然而,不幸的是,目前没有一种经验法则可以提前知道正确的值应该是多少——我们只能通过试验和错误来找到它。
梯度基优化器主要有两种类型:批量梯度下降和随机梯度下降。在我们深入讨论这两种之前,先回顾一下一个训练周期(epoch)的含义:它表示神经网络遍历所有训练样本的一次训练迭代。
-
在一个训练周期内,当我们减少所有训练样本的损失时,这就是批量梯度下降。它也被称为全批量梯度下降。简单来说,遍历完一个完整批次后,我们会采取一步来调整网络的权重和偏置,以减少损失并改善预测。还有一种类似的方法叫做小批量梯度下降,它是在遍历数据集的一个子集后调整权重和偏置的过程。
-
与批量梯度下降不同,当我们每次迭代只取一个样本时,就有了随机梯度下降(SGD)。随机一词告诉我们这里涉及随机性,在这种情况下,就是随机选择的批量。
尽管随机梯度下降(SGD)相对有效,但还有一些先进的优化器可以加速训练过程。它们包括带动量的 SGD、Adagrad 和 Adam。
消失梯度问题
在感知机训练部分,我们了解了神经网络的前向传播和反向传播。当神经网络进行前向传播时,误差梯度是相对于真实标签计算的,之后进行反向传播,查看神经网络的哪些参数(权重和偏置)对误差的贡献以及贡献的大小。误差梯度从输出层传播到输入层,计算每个参数的梯度,最后一步是执行梯度下降步骤,根据计算得到的梯度调整权重和偏置。随着误差梯度的反向传播,计算得出的每个参数的梯度逐渐变小,直到更低(初始)层次。这种梯度的减小意味着权重和偏置的变化也变得越来越小。因此,我们的神经网络很难找到全局最小值,并且结果不好。这就是所谓的梯度消失问题。这个问题出现在使用 sigmoid(逻辑)函数作为激活函数时,因此我们使用 ReLU 激活函数来训练深度神经网络模型,以避免梯度问题并改善结果。
超参数调优
像机器学习中的其他模型训练过程一样,我们可以进行超参数调优,以提高神经网络模型的性能。其中一个参数是学习率。其他参数如下:
-
迭代次数:增加迭代次数通常能提高准确性并降低损失
-
层数:增加层数可以提高准确性,正如我们在 MNIST 练习中看到的那样
-
每层的神经元数量:这也会提高准确性
再次强调,我们无法事先知道正确的层数或每层神经元的数量。必须通过试错法来找出。需要注意的是,层数越多,每层的神经元数量越多,所需的计算能力就越大。因此,我们从最小的数字开始,逐步增加层数和神经元数量。
过拟合与 Dropout
神经网络具有复杂的架构和过多的参数,容易在所有数据点上进行过拟合,包括噪声标签,从而导致过拟合问题,并使神经网络无法在未见过的数据集上进行良好的泛化。为了解决这个问题,有一种技术叫做dropout:
图 2.36:Dropout 说明
在该技术中,训练过程中会随机停用一定数量的神经元。要停用的神经元数量通过百分比的形式作为参数提供。例如,Dropout = .2 表示该层中 20% 的神经元将在训练过程中随机停用。相同的神经元不会被多次停用,而是在每个训练周期中停用不同的神经元。然而,在测试过程中,所有神经元都会被激活。
下面是我们如何使用 Keras 将 Dropout 添加到神经网络模型中的示例:
model.add(Dense(units = 300, activation = 'relu')) #Hidden layer1
model.add(Dense(units = 200, activation = 'relu')) #Hidden Layer2
model.add(Dropout(.20))
model.add(Dense(units = 100, activation = 'relu')) #Hidden Layer3
在这种情况下,Hidden Layer2 添加了 20% 的 dropout。并不需要将 dropout 添加到所有层。作为数据科学家,您可以进行实验并决定 dropout 值应该是多少,以及需要多少层。
注意
有关 dropout 的更详细解释可以在 Nitish Srivastava 等人的论文中找到,点击此链接即可查看:www.jmlr.org/papers/volume15/srivastava14a/srivastava14a.pdf。
随着我们进入本章的尾声,让我们通过以下活动来测试目前为止学到的内容。
活动 2.01:构建一个多层神经网络来分类声纳信号
在此活动中,我们将使用 Sonar 数据集(archive.ics.uci.edu/ml/datasets/Connectionist+Bench+(Sonar,+Mines+vs.+Rocks)),该数据集通过将声纳信号以不同角度和条件反射到金属圆柱体上获得。您将构建一个基于神经网络的分类器,用于区分从金属圆柱体反射回来的声纳信号(矿物类)和从大致圆柱形的岩石反射回来的声纳信号(岩石类)。我们建议使用 Keras API 来使您的代码更加易读和模块化,这样您可以轻松地尝试不同的参数:
注意
您可以通过以下链接下载 sonar 数据集:packt.live/31Xtm9M。
-
第一步是理解数据,以便您能确定这是二分类问题还是多分类问题。
-
一旦您理解了数据和需要执行的分类类型,下一步就是网络配置:神经元的数量、隐藏层的数量、使用哪个激活函数等等。
回顾我们迄今为止讨论的网络配置步骤。让我们再强调一个关键点——激活函数部分:对于输出(最后一层),我们使用 sigmoid 进行二分类,使用 Softmax 进行多分类。
-
打开
sonar.csv文件以探索数据集,并查看目标变量是什么。 -
分离输入特征和目标变量。
-
对数据进行预处理,使其兼容神经网络。提示:one-hot 编码。
-
使用 Keras 定义一个神经网络,并使用正确的损失函数进行编译。
-
打印出模型总结以验证网络参数和 注意事项。
你应通过使用这些步骤设计一个合适的多层神经网络,以期获得 95%以上的准确率。
注意
本活动的详细步骤、解决方案和额外评论已在第 390 页呈现。
总结
在本章中,我们首先研究了生物神经元,然后转向人工神经元。我们了解了神经网络的工作原理,并采用实践的方法构建了单层和多层神经网络来解决监督学习任务。我们研究了感知机的工作原理,它是神经网络的一个单元,直到可以进行多类分类的深度神经网络。我们看到 Keras 如何使得使用最少的代码轻松创建深度神经网络。最后,我们研究了在构建成功的神经网络时需要考虑的实际因素,这涉及了诸如梯度下降优化器、过拟合和 Dropout 等重要概念。
在下一章,我们将进入更高层次,构建一个更复杂的神经网络,称为 CNN,它广泛应用于图像识别。