diff --git a/docs/handson-tl-py/0.md b/docs/handson-tl-py/0.md new file mode 100644 index 0000000000000000000000000000000000000000..7f3141821cffcce4fc6c4634614fc18d93d60534 --- /dev/null +++ b/docs/handson-tl-py/0.md @@ -0,0 +1,95 @@ +# 前言 + +随着世界朝着数字化和自动化的方向发展,作为技术专家/程序员,保持自我更新并学习如何利用这些工具和技术非常重要。 本书*使用 Python 的动手传递学习*旨在帮助从业人员熟悉并准备好在各自领域中使用这些进步。 本书大致分为三个部分: + +* 深度学习基础 +* 转移学习的要点 +* 转移学习案例研究 + +转移学习是一种**机器学习**( **ML** )技术,其中在训练一组 ML 问题中获得的知识可以用于训练其他类似类型的问题。 + +这本书的目的是双重的。 我们将专注于深度学习和迁移学习的详细介绍,并通​​过易于理解的概念和示例将两者进行比较和对比。 第二个重点领域是使用 TensorFlow,Keras 和 Python 生态系统(带动手实例)的实际示例和研究问题。 + +本书从 ML 和深度学习的核心基本概念开始,然后是一些重要的深度学习架构的描述和覆盖,例如 CNN,DNN,RNN,LSTM 和胶囊网络。 然后,我们的重点转移到转移学习概念和经过培训的*先进网络*,例如 VGG,Inception 和 ResNet。 我们还将学习如何利用这些系统来改善我们的深度学习模型的性能。 最后,我们重点关注**计算机视觉**,**音频分析**和**自然语言处理**( **NLP** )。 + +到本书结尾,您将准备好在自己的系统中既实现深度学习又实现迁移学习的原则。 + +# 这本书是给谁的 + +*使用 Python 的动手转移学习*适用于对数据感兴趣并应用最先进的转移学习方法来解决实际难题的数据科学家,ML 工程师,分析师和开发人员。 + +需要具备 ML 和 Python 的基本知识。 + +# 本书涵盖的内容 + +[第 1 章](../Text/1.html)和*机器学习基础*介绍了 CRISP-DM 模型,该模型为任何数据科学,ML 或深度学习项目提供了行业标准框架/工作流程。 我们还将涉及涵盖 ML 领域基础知识的各种重要概念,例如探索性数据分析,特征提取和工程,评估指标等。 + +[第 2 章](../Text/2.html)和*深度学习基础知识*提供了深度学习基础知识的旋风之旅,概述了神经网络的基本组成部分以及如何训练深度神经网络。 从单个神经单元工作原理的基础开始,涵盖了重要的概念,例如激活函数,损失函数,优化器和神经网络超参数。 还特别强调建立本地和基于云的深度学习环境。 + +[第 3 章](../Text/3.html),*了解深度学习架构*着重于理解当今在深度学习中使用的各种标准模型架构。 自 1960 年代的传统人工神经网络以来,我们已经走了很长一段路,而基本模型架构,例如完全连接的**深层神经网络**( **DNN** ),**卷积神经网络**( **CNN** ,**递归神经网络**( **RNN** ),**长期记忆**( **LSTM** )网络, 仅举几例,将介绍最新的**胶囊网络**。 + +[第 4 章](../Text/4.html)和*转移学习基础*着眼于与转移学习概念相关的核心概念,术语和模型架构。 将详细讨论与预训练模型有关的概念和体系结构。 我们还将把迁移学习与深度学习进行比较和对比,并讨论迁移学习的类型和策略。 + +[第 5 章](../Text/5.html),*释放转移学习的力量*,以来自 Kaggle 的数据集为例,利用深度学习模型,使读者了解我们面临的挑战 具有少量数据点,以及转移学习如何释放其真正的力量和潜力,以在这些情况下为我们提供出色的模型。 在这里,我们将通过较少的数据可用性约束来解决非常受欢迎的猫猫分类任务。 + +,*图像识别和分类*是在本书的前两部分中详细讨论的概念的一系列实际应用/案例研究中的第一篇。 本章首先介绍了图像分类的任务,然后继续讨论并实现了针对各种图像分类问题的一些流行的,最新的深度学习模型。 + +[第 7 章](../Text/7.html)和*文本文档分类*讨论了转移学习在非常流行的自然语言处理问题文本文档分类中的应用。 本章从高层次介绍多类别文本分类问题,传统模型,基准文本分类数据集(例如 20 个新闻组)和性能开始。 随后,它介绍了用于文本分类的深度学习文档模型及其相对于传统模型的优势。 我们将学习使用密集向量的单词特征表示,以及如何在文本分类问题中如何利用相同特征来应用转移学习,从而使源域和目标域可能有所不同。 还描述了其他无监督的任务,例如文档摘要。 + +[第 8 章](../Text/8.html),*音频识别和分类*解决了识别和分类非常短的音频剪辑的难题。 在这里,我们利用一些创新技术来利用转移学习,这些创新技术将来自计算机视觉领域的预训练深度学习模型的功能应用于完全不同的音频识别领域。 + +[第 9 章](../Text/9.html), *DeepDream* 着重介绍了生成型深度学习的领域,这是真正人工智能最前沿的核心思想之一。 我们将重点研究卷积神经(CNN)如何通过转移学习来思考或梦想和形象化图像中的模式。 由 Google 于 2015 年首次发布,由于深层网络开始从图像中产生有趣的模式,仿佛自己在思考和做梦,它引起了轰动。 + +[第 10 章](../Text/9.html),*样式转移*利用来自深度学习,转移学习和生成学习的概念,通过动手实例在不同内容图像上展示艺术图像神经样式转移,并 样式。 + +和*自动图像字幕生成器*涵盖了计算机视觉以及自然语言生成中最复杂的问题之一-图像字幕。 虽然将图像分类为固定类别具有挑战性但并非不可能,但这是一个稍微复杂的任务,涉及为任何照片或场景生成类似于人类的自然语言文字说明。 利用迁移学习,自然语言处理和生成模型的功能,您将学习如何从头开始构建自己的自动图像字幕系统。 + +[第 12 章](../Text/11.html),*图像着色*提供了一个独特的案例研究,其任务是为黑白或灰度图像着色。 本章向读者介绍各种色标的基础知识,以及为什么图像着色是一项如此困难的任务。 + +# 充分利用这本书 + +1. 如果您精通 ML 和 Python,那就太好了。 +2. 对数据分析,机器学习和深度学习的浓厚兴趣将是有益的。 + +# 使用约定 + +本书中使用了许多文本约定。 + +`CodeInText`:指示文本,数据库表名称,文件夹名称,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄中的代码字。 这是一个示例:“完成后,请记住使用您喜欢的编辑器(我们使用`vim`)在`~/.bashrc`的末尾添加以下行。” + +代码块设置如下: + +```py +import glob +import numpy as np +import os +import shutil +from utils import log_progress + +np.random.seed(42) +``` + +当我们希望引起您对代码块特定部分的注意时,相关的行或项目以粗体显示: + +```py +preprocessor = Preprocess() +corpus_to_seq = preprocessor.fit(corpus=corpus) + +holdout_corpus = test_df['review'].values +holdout_target = test_df['sentiment'].values +holdout_corpus_to_seq = preprocessor.transform(holdout_corpus) +``` + +任何命令行输入或输出的编写方式如下: + +```py +ubuntu@ip:~$ mkdir ssl +ubuntu@ip:~$ cd ssl +ubuntu@ip:~/ssl$ +``` + +**粗体**:表示新术语,重要单词或您在屏幕上看到的单词。 例如,菜单或对话框中的单词会出现在这样的文本中。 这是一个示例:“启动实例后,您可以签出“实例”部分并尝试连接到实例。” + +警告或重要提示如下所示。 + +提示和技巧如下所示。 diff --git a/docs/handson-tl-py/1.md b/docs/handson-tl-py/1.md new file mode 100644 index 0000000000000000000000000000000000000000..9e59291aa6e11806fb2027c7b294e4bda8b0fbe5 --- /dev/null +++ b/docs/handson-tl-py/1.md @@ -0,0 +1,732 @@ +# 机器学习基础 + +有一天,人工智能将像看非洲平原上的化石骨架一样回望我们。 一只生活在尘土中的直立猿,用粗俗的语言和工具灭绝。 + +–内森·贝特曼(Nathan Bateman),前 Machina(电影节 2014) + +这句话似乎夸大了核心内容,难以理解,但是随着技术和科学的进步,谁知道呢? 作为一个物种,我们一直梦想创造出智能的,具有自我意识的机器。 随着研究,技术和计算能力民主化的最新发展,**人工智能**( **AI** ),**机器学习**( **ML** ) 深度学习已在技术人员和一般人群中引起了极大的关注和炒作。 尽管好莱坞承诺的未来值得商,,但我们已经开始在日常生活中看到和使用智能系统。 从诸如 Google Now,Siri,Alexa 和 Cortana 之类的智能对话引擎,到无人驾驶汽车,我们正在日常工作中逐渐接受这种智能技术。 + +随着我们步入学习机器的新时代,重要的是要了解基本概念和概念已经存在了一段时间,并且已经被地球上的聪明人不断改进。 众所周知,全球数据的 90%是在最近几年中创建的,并且我们将以越来越高的速度继续创建更多的数据。 机器学习,深度学习和人工智能领域帮助我们利用这些海量数据来解决各种现实问题。 + +本书分为三个部分。 在第一部分中,我们将开始与 AI,ML 和深度学习相关的基本概念和术语,然后是深度学习架构的深入细节。 + +本章为读者提供了有关 ML 基本概念的快速入门,然后在后续各章中开始进行深度学习。 本章涵盖以下方面: + +* ML 简介 +* 机器学习方法 +* CRISP-DM-机器学习项目的工作流程 +* ML 管道 +* 探索性数据分析 +* 特征提取与工程 +* 功能选择 + +本书的每一章都基于前几章的概念和技术。 精通 ML 和深度学习基础知识的读者可以选择他们认为必要的主题,但建议顺序阅读这些章节。 您可以在 GitHub 存储库中的 [https://github.com/dipanjanS/hands-on-transfer-learning-with-python](https://github.com/dipanjanS/hands-on-transfer-learning-with-python) 的 GitHub 存储库的`Chapter 1`文件夹中快速阅读本章的代码。 可以根据需要参考本章。 + +# 为什么要 ML? + +我们生活的世界中,我们的日常工作涉及与数字世界的多个接触点。 我们有计算机协助我们进行通讯,旅行,娱乐等。 我们一直无缝使用的数字在线产品(应用程序,网站,软件等)帮助我们避免了平凡而又重复的任务。 这些软件已由程序员明确使用每条指令进行编程以使这些软件能够执行定义的任务,并使用计算机编程语言(如 C,C ++,Python,Java 等)开发。 下图描述了计算设备(计算机,电话等)与带有输入和定义的输出的显式编程的软件应用程序之间的典型交互: + +![](img/5da17bd5-6922-48a9-b779-3cfaff2c2a45.png) + +传统编程范例 + +尽管当前的范例已经在帮助我们开发出惊人的复杂软件/系统,以一种非常有效的方式来解决来自不同领域和方面的任务,但它们仍需要有人为此类程序定义并编写明确的规则才能工作。 这些任务对于计算机来说很容易解决,但对人类来说却是困难或费时的。 例如,执行复杂的计算,存储大量数据,通过庞大的数据库进行搜索等等都是可以在定义规则后由计算机有效执行的任务。 + +然而,还有另一类问题可以由人类直观地解决,但是很难编程。 对象识别,玩游戏等问题对我们来说很自然,但很难用一组规则来定义。 艾伦·图灵(Alan Turing)在他的地标性论文*计算机械和智能*( [https://www.csee.umbc.edu/courses/471/papers/turing.pdf](https://www.csee.umbc.edu/courses/471/papers/turing.pdf) )中进行了介绍 **图灵测试**讨论了通用计算机以及它们是否能够执行此类任务。 + +这种体现了通用计算思想的新范式在更广泛的意义上促成了 AI 的产生。 这种新的范式,更好地称为 ML 范式,是计算机或机器从经验(类似于人类学习)中学习来解决任务的方法,而不是经过明确编程才能做到的。 + +因此,人工智能是研究的一个涵盖领域,而机器学习和深度学习是其中的特定研究子领域。 AI 是一个通用字段,还包括其他子字段,可能涉及也可能不涉及学习(例如,参见符号 AI)。 在本书中,我们将把时间和精力仅用于 ML 和深度学习。 人工智能,机器学习和深度学习的范围可以可视化如下: + +![](img/a731f64c-9ff7-4540-a4b4-caad445dde92.png) + +人工学习的范围,以机器学习和深度学习为子领域 + +# 正式定义 + +由汤姆·米切尔(Tom Mitchell)提出的 ML 的正式定义解释如下。 + +如果计算机程序在 *T* 上的性能,则据说该计算机程序可以从 *E* 的经验中学习某些任务 *T* 和某些性能指标 *P* 。 *P* 所衡量的,会随着经验 *E* 而提高。 + +该定义以非常简洁的方式很好地捕捉了 ML 的本质。 让我们以现实世界为例,以更好地理解它。 让我们考虑一个任务( *T* )是识别垃圾邮件。 现在,我们可能会向有关垃圾邮件和非垃圾邮件的系统提供许多示例(或经验),从中可以学习而不是对其进行明确编程。 然后可以在学习到的识别垃圾邮件的任务上对程序或系统的性能进行测量( *P* )。 有趣,不是吗? + +# 浅层和深度学习 + +因此,ML 的任务是从训练示例中识别模式,并将这些学习到的模式(或表示形式)应用于新的看不见的数据。 ML 有时也称为**浅层学习**,因为它学习*单层表示形式*的性质(在大多数情况下)。 这使我们想到*的表示层是什么?* 和*什么是深度学习?* 我们将在后续章节中回答这些问题。 让我们快速了解一下深度学习。 + +深度学习是机器学习的一个子领域,它涉及从训练示例中学习连续的有意义的表示,以解决给定的任务。 深度学习与人工神经网络紧密相关,人工神经网络由一个接一个的堆叠的多层组成,这些层捕获连续的表示。 + +如前所述,不要担心是否难以理解和理解,我们将在后续章节中更深入地介绍。 + +由于我们正在生成和收集的数据量以及更快的计算能力,ML 已成为流行语。 在以下各节中,让我们更深入地研究 ML。 + +# 机器学习技术 + +ML 是 AI 的一个流行子领域,涵盖了非常广泛的领域。 如此受欢迎的原因之一是在其能力范围内的复杂算法,技术和方法的综合工具箱。 多年来,该工具箱已经得到开发和改进,并且正在不断研究新的工具箱。 为了明智地理解和使用 ML 工具箱,请考虑以下几种分类方法。 + +基于人工监督量的分类: + +* **有监督的学习**:此类学习涉及人为监督。 在监督学习下的算法利用训练数据和相关的输出来学习两者之间的映射,并将其应用于看不见的数据。 分类和回归是监督学习算法的两种主要类型。 +* **无监督学习**:此类算法尝试从输入数据中学习固有的潜在结构,模式和关系,而没有任何关联的输出/标签(人工监督)。 聚类,降维,关联规则挖掘等是无监督学习算法的几种主要类型。 +* **半监督学习**:此类算法是监督学习和无监督学习的混合体。 在这种情况下,该算法可处理少量带标签的训练数据和更多未标记的数据。 因此创造性地利用了监督和非监督方法来解决给定的任务。 +* **强化学习**:这类算法与有监督和无监督学习方法有些不同。 这里的中心实体是一个代理,它会在一段时间内进行训练,同时与环境互动以最大化奖励/奖励。 代理基于与环境的交互所获得的奖励/惩罚,反复学习和更改策略/策略。 + +根据数据可用性进行分类: + +* **批量学习**:这也称为**离线学习**。 当所需的培训数据可用时,将使用这种类型的学习,并且可以在部署到生产/现实世界中之前对模型进行培训和微调。 +* **在线学习**:顾名思义,在这种情况下,一旦数据可用,学习就不会停止。 而是在这种情况下,将数据以小批量的形式输入到系统中,并且训练过程将使用新的一批数据继续进行。 + +前面讨论的分类为我们提供了如何组织,理解和利用 ML 算法的抽象视图。 将它们分类的最常见方法是有监督和无监督学习算法。 让我们更详细地介绍这两个类别,因为这将有助于我们开始进一步的高级主题,稍后再进行介绍。 + +# 监督学习 + +监督学习算法是一类利用数据样本(也称为**训练样本**)和相应的输出(或**标签**)推断两者之间映射功能的算法。 推断的映射函数或学习的函数是此训练过程的输出。 然后,将学习的功能用于正确映射新的和看不见的数据点(输入元素)以测试学习的功能的性能。 + +监督学习算法的一些关键概念如下: + +* **训练数据集**:在训练过程中使用的训练样本和相应的输出称为**训练数据**。 形式上,训练数据集是一个由两个元素组成的元组,由输入元素(通常是矢量)和相应的输出元素或信号组成。 +* **测试数据集**:用于测试学习功能性能的未知数据集。 该数据集也是一个包含输入数据点和相应输出信号的二元组。 该集合中的数据点不用于训练阶段(此数据集也进一步分为验证集合;我们将在后续章节中对此进行详细讨论)。 +* **学习的功能**:这是训练阶段的输出。 也称为*推断功能*或*模型*。 根据训练数据集中的训练示例(输入数据点及其对应的输出)推断此功能。 理想的模型/学习的函数将以这样的方式学习映射,即可以将结果也推广到看不见的数据。 + +有多种可用的监督学习算法。 根据用例需求,可以将它们主要分类为分类和回归模型。 + +# 分类 + +简而言之,这些算法可帮助我们回答*客观*问题或*是-否*预测。 例如,这些算法在*这样的场景下有用吗?* 或*该肿瘤会癌变吗?* 等。 + +形式上,分类算法的主要目标是根据输入数据点预测本质上属于分类的输出标签。 输出标签本质上是分类的; 也就是说,它们每个都属于离散的类或类别。 + +**Logistic 回归**,**支持向量机**( **SVM** ),**神经网络**,**随机森林**, **k 最近邻居**( **KNN** ),**决策树**等是一些流行的分类算法。 + +假设我们有一个真实的用例来评估不同的汽车模型。 为简单起见,让我们假设该模型可以根据多个输入训练样本预测每种汽车模型的输出是可接受的还是不可接受的。 输入的训练样本具有诸如购买价格,门数,容量(以人数计)和安全性的属性。 + +除类标签之外的级别将每个数据点表示为可接受或不可接受。 下图描述了即将出现的二进制分类问题。 分类算法将训练样本作为输入以准备监督模型。 然后,利用该模型来预测新数据点的评估标签: + +![](img/259513b0-c48f-487c-898c-780cf7700b29.png) + +监督学习:用于汽车模型评估的二进制分类 + +由于在分类问题中输出标签是离散类,因此如果只有两个可能的输出类,则该任务称为**二进制分类问题**,否则称为多类分类。 预测明天是否下雨将是一个二进制分类问题(输出是“是”或“否”),而从扫描的手写图像中预测数字将是具有 10 个标签(零到九个可能的输出标签)的多类分类。 。 + +# 回归 + +此类有监督的学习算法有助于我们回答*数量*或*类型的*定量*问题。* 正式而言,回归模型的关键目标是价值估算。 在这种情况下,输出标签本质上是连续的(相对于分类而言是离散的)。 + +在回归问题的情况下,输入数据点称为*独立*或*解释变量*,而输出称为*因变量*。 还使用训练数据样本来训练回归模型,该训练数据样本包括输入(或独立)数据点以及输出(或相关)信号。 线性回归,多元回归,回归树等是一些监督式回归算法。 + +可以基于建模模型如何对因变量和自变量之间的关系建模来进一步分类。 + +**简单线性回归**模型适用于单个自变量和单个因变量。 **普通最小二乘**( **OLS** )回归是一种流行的线性回归模型。 多元回归或多元回归是只有一个因变量的地方,而每个观察值都是由多个解释变量组成的向量。 + +多项式回归模型是多元回归的一种特殊情况。 在此,因变量被建模为自变量的第 n 度。 由于多项式回归模型拟合或映射因变量和自变量之间的非线性关系,因此这些也称为**非线性回归**模型。 + +以下是线性回归的示例: + +![](img/6174260e-2892-4fe2-9cb9-a687760ee2a9.png) + +监督学习:线性回归 + +为了理解不同的回归类型,让我们考虑一个真实的用例,它基于汽车的速度来估计汽车的停止距离。 在这里,根据我们拥有的训练数据,我们可以将停车距离建模为速度的线性函数或汽车速度的多项式函数。 请记住,主要目的是在不过度拟合训练数据本身的情况下最大程度地减少错误。 + +上图描述了线性拟合,而下图描述了同一数据集的多项式拟合: + +![](img/b3ad80ab-a9db-4728-bd61-8a91790e03b5.png) + +监督学习:多项式回归 + +# 无监督学习 + +顾名思义,此类算法无需监督即可学习/推断概念。 与监督学习算法基于包含输入数据点和输出信号的训练数据集推断映射功能不同,无监督算法的任务是在训练数据中查找模式和关系,而训练数据集中没有可用的输出信号。 这类算法利用输入数据集来检测模式,并挖掘规则或组/集群数据点,以便从原始输入数据集中提取有意义的见解。 + +当我们没有包含相应输出信号或标签的训练集的自由时,无监督算法会派上用场。 在许多实际场景中,可以使用没有输出信号的数据集,很难手动标记它们。 因此,无监督算法有助于弥补这种差距。 + +与监督学习算法类似,也可以对非监督算法进行分类,以易于理解和学习。 以下是无监督学习算法的不同类别。 + +# 聚类 + +分类的无监督等效项称为**聚类**。 这些算法可以帮助我们将数据点聚类或分组为不同的组或类别,而无需在输入/训练数据集中使用任何输出标签。 这些算法尝试使用固有特征基于某种相似性度量将输入数据集中的模式和关系查找到不同的组中,如下图所示: + +![](img/a19a6e64-1663-4055-8823-101a8af77049.png) + +无监督学习:将新闻文章聚类 + +有助于理解群集的真实示例可以是新闻文章。 每天有数百篇新闻文章,每一篇都涉及从政治,体育到娱乐等不同主题。 如上图所示,可以使用聚类来实现将这些物品组合在一起的无监督方法。 + +有多种执行聚类过程的方法。 最受欢迎的是: + +* 基于质心的方法。 流行的是 K-均值和 K-medoids。 +* 聚集和分裂的层次聚类方法。 流行的是沃德氏和亲和力传播。 +* 基于数据分布的方法,例如高斯混合模型。 +* 基于密度的方法,例如 DBSCAN 等。 + +# 降维 + +数据和 ML 是最好的朋友,但是越来越多的数据带来了很多问题。 大量的属性或膨胀的特征空间是一个常见问题。 较大的特征空间在分析和可视化数据时会带来问题,以及与训练,内存和空间限制有关的问题。 这也被称为**维度诅咒**。 由于无监督方法可以帮助我们从未标记的训练数据集中提取见解和模式,因此它们也有助于我们减少维度。 + +换句话说,无监督方法通过帮助我们从完整的可用列表中选择一组代表性的特征来帮助我们减少特征空间: + +![](img/11b7469a-fafe-40c9-a0c8-ea3d9f083f33.png) + +无监督学习:使用 PCA 减少维度 + +**主成分分析**( **PCA** ),最近邻和判别分析是一些流行的降维技术。 + +上图是对基于 PCA 的降维技术工作的著名描述。 它显示了瑞士卷形状,其中数据以三维空间表示。 PCA 的应用导致将数据转换为二维空间,如图的右侧所示。 + +# 关联规则挖掘 + +这类无监督的 ML 算法有助于我们理解事务数据集并从中提取模式。 这些算法也称为**市场篮子分析**( **MBA** ),可帮助我们识别跨交易的项目之间有趣的关系和关联。 + +使用关联规则挖掘,我们可以回答诸如*之类的问题,人们在给定商店中一起购买了哪些物品?* 或*购买葡萄酒的人也倾向于购买奶酪吗?* 等等。 FP-growth,ECLAT 和 Apriori 是用于关联规则挖掘任务的最广泛使用的算法。 + +# 异常检测 + +异常检测是根据历史数据识别罕见事件/观测的任务。 异常检测也称为**离群值检测**。 异常或离群值通常具有以下特征:不经常发生或随时间推移而突然突然爆发。 + +对于此类任务,我们为算法提供了历史数据集,因此它可以以无监督的方式识别和了解数据的正常行为。 学习后,该算法将帮助我们识别与该学习行为不同的模式。 + +# CRISP-DM + +**跨行业数据挖掘标准流程**( **CRISP-DM** )是数据挖掘和分析项目中最流行且使用最广泛的流程之一。 CRISP-DM 提供了所需的框架,该框架清楚地概述了执行数据挖掘和分析项目所需的步骤和工作流程,从业务需求到最终部署阶段以及介于两者之间的所有内容。 + +CRISP-DM 以首字母缩略词本身更广为人知,是一种经过尝试,测试且可靠的行业标准过程模型,适用于数据挖掘和分析项目。 CRISP-DM 清楚地描述了执行任何项目所必需的步骤,过程和工作流,从正式的业务需求到测试和部署解决方案以将数据转化为见解。 数据科学,数据挖掘和 ML 都是要尝试运行多个迭代过程以从数据中提取见解和信息。 因此,我们可以说,分析数据的确是一门艺术,也是一门科学,因为它并非总是无缘无故地运行算法。 许多主要工作涉及了解业务,所投入工作的实际价值以及表达最终结果和见解的正确方法。 + +数据科学和数据挖掘项目本质上是迭代的,以从数据中提取有意义的见解和信息。 数据科学与科学一样具有艺术性,因此在应用实际算法(再次经过多次迭代)并最终进行评估和部署之前,需要花费大量时间来了解业务价值和手头的数据。 + +与具有不同生命周期模型的软件工程项目相似,CRISP-DM 可帮助我们从头到尾跟踪数据挖掘和分析项目。 该模型分为六个主要步骤,涵盖从业务和数据理解到评估和最终部署的各个方面,所有这些本质上都是迭代的。 请参见下图: + +![](img/47410ec4-28c7-4e91-9152-23363f957e63.png) + +描述 ML 项目工作流程的 CRISP-DM 模型 + +现在让我们更深入地研究六个阶段中的每个阶段,以更好地了解 CRISP-DM 模型。 + +# 业务了解 + +第一步也是最重要的一步是了解业务。 这一关键步骤始于设置业务环境和问题要求。 正式定义业务需求对于将其转换为数据科学和分析问题陈述至关重要。 此步骤还用于为业务和数据科学团队设置期望和成功标准,使其位于同一页面上并跟踪项目进度。 + +此步骤的主要交付成果是详细计划,包括主要里程碑,时间表,假设,约束,警告,预期问题和成功标准。 + +# 数据理解 + +数据收集和理解是 CRISP-DM 框架的第二步。 在这一步中,我们将进行更深入的了解,以了解和分析上一步中形式化的问题陈述的数据。 此步骤开始于调查先前详细项目计划中概述的各种数据源。 然后将这些数据源用于收集数据,分析不同的属性并记录数据质量。 此步骤还涉及通常称为探索性数据分析的内容。 + +**探索性数据分析**( **EDA** )是非常重要的子步骤。 在 EDA 期间,我们分析了数据的不同属性,属性和特征。 我们还将 EDA 期间的数据可视化,以更好地理解和发现以前可能看不见或忽略的模式。 此步骤为后续步骤奠定了基础,因此,这一步骤根本不能忽略。 + +# 资料准备 + +这是任何数据科学项目中的第三步,也是最耗时的步骤。 一旦我们了解了业务问题并探索了可用数据,便会进行数据准备。 此步骤涉及数据集成,清理,整理,特征选择和特征工程。 首先,最重要的是数据集成。 有时候,可以从各种来源获得数据,因此需要根据某些键或属性进行组合以更好地使用。 + +数据清理和整理是非常重要的步骤。 这涉及处理缺失值,数据不一致,修复不正确的值以及将数据转换为可摄取格式,以便 ML 算法可以使用它们。 + +数据准备是最耗时的步骤,占任何数据科学项目总时间的 60-70%以上。 除了数据集成和处理之外,此步骤还包括根据相关性,质量,假设和约束条件选择关键功能。 这也称为**特征选择**。 有时候,我们不得不从现有特征中衍生或生成特征。 例如,根据用例要求,从出生日期算起年龄等等。 此步骤称为**特征工程**,并且根据用例再次需要此步骤。 + +# 造型 + +第四步或建模步骤是进行实际分析和机器学习的地方。 此步骤将在上一步中准备的干净和格式化的数据用于建模目的。 这是一个迭代过程,与数据准备步骤同步工作,因为模型/算法需要具有不同属性集的不同设置/格式的数据。 + +此步骤涉及选择相关工具和框架,以及选择建模技术或算法。 此步骤包括基于业务理解阶段中确定的期望和标准进行模型构建,评估和模型微调。 + +# 评价 + +一旦建模步骤生成满足成功标准,性能基准和模型评估指标的模型,就需要进行彻底的评估。 在此步骤中,我们将在进行部署阶段之前考虑以下活动: + +* 基于质量并与业务目标保持一致来对结果评估进行建模 +* 确定做出的任何其他假设或放松约束 +* 数据质量,信息丢失以及数据科学团队和/或**主题专家**(**中小企业**)的其他反馈 +* 端到端 ML 解决方案的部署成本 + +# 部署方式 + +CRISP-DM 模型的最后一步是部署到生产。 在多次迭代过程中开发,微调,验证和测试的模型将保存起来并准备用于生产环境。 构建了适当的部署计划,其中包括有关硬件和软件要求的详细信息。 部署阶段还包括进行检查和监视方面,以评估生产中的模型的结果,性能和其他指标。 + +# 标准 ML 工作流程 + +CRISP-DM 模型提供了用于 ML 和相关项目管理的高级工作流。 在本节中,我们将讨论用于处理 ML 项目的技术方面和标准工作流的实现。 简而言之,ML 管道是一个端到端的工作流程,由数据密集型项目的各个方面组成。 一旦涵盖了业务理解,风险评估以及 ML 或数据挖掘技术选择等初始阶段,我们便会着手推动项目的解决方案空间。 下图显示了具有不同子组件的典型 ML 管道或工作流: + +![](img/f366040e-1ec5-4a7b-9d0e-277f00c308e1.png) + +典型的 ML 管道 + +标准 ML 管道大致包括以下阶段。 + +# 资料检索 + +数据收集和提取通常是故事的起点。 数据集有各种形式,包括结构化和非结构化数据,这些数据通常包括丢失或嘈杂的数据。 每种数据类型和格式都需要特殊的机制来进行数据处理和管理。 例如,如果一个项目涉及对推文的分析,我们需要使用 Twitter API 并开发机制以提取所需的推文,这些推文通常为 JSON 格式。 + +其他场景可能涉及已经存在的结构化或非结构化公共数据集或私有数据集,除了仅开发提取机制外,这两种情况都可能需要其他权限。 Sarkar 和他们的合著者 Springer 在本书的*《使用 Python 进行机器学习》(HTG1)的第 3 章中讨论了与使用各种数据格式有关的相当详细的内容,如果您有兴趣进一步研究,请参阅 2017 年的 Springer。 更多详细信息。* + +# 资料准备 + +值得重申的是,这是在整个管道中花费最大时间的地方。 这是一个相当详细的步骤,涉及基本和重要的子步骤,其中包括: + +* 探索性数据分析 +* 数据处理与整理 +* 特征工程和提取 +* 特征缩放和选择 + +# 探索性数据分析 + +到目前为止,项目的所有初始步骤都围绕业务环境,需求,风险等。 这是我们实际深入研究收集/可用数据的第一个接触点。 EDA 帮助我们了解数据的各个方面。 在此步骤中,我们将分析数据的不同属性,发现有趣的见解,甚至可视化不同维度上的数据以获得更好的理解。 + +此步骤可帮助我们收集手头的数据集的重要特征,这不仅在项目的后期阶段很有用,而且还有助于我们在管道早期识别和/或缓解潜在问题。 我们将在本章后面介绍一个有趣的示例,以使读者了解 EDA 的过程和重要性。 + +# 数据处理与整理 + +此步骤与将数据转换为可用形式有关。 在大多数情况下,ML 算法无法使用第一步中检索到的原始数据。 形式上,数据整理是将数据从一种形式清除,转换和映射为另一种形式,以便在项目生命周期的后期阶段使用的过程。 此步骤包括缺少数据插补,类型转换,处理重复项和异常值,等等。 为了更好的理解,我们将在用例驱动的章节中介绍这些步骤。 + +# 特征工程和提取 + +经过预处理和处理后的数据达到了可以被特征工程和提取步骤利用的状态。 在此步骤中,我们利用现有属性来导出和提取上下文/用例特定的属性或特征,这些属性或特征可以在接下来的阶段中被 ML 算法利用。 我们根据数据类型采用不同的技术。 + +特征工程和提取是一个相当复杂的步骤,因此在本章的后面部分将进行更详细的讨论。 + +# 特征缩放和选择 + +在某些情况下,可用功能的数量过大,会对整个解决方案产生不利影响。 具有大量属性的数据集的处理和处理不仅成为问题,而且还导致解释,可视化等方面的困难。 这些问题被正式称为维度**的诅咒**。 + +因此,特征选择可以帮助我们确定可以在建模步骤中使用的代表性特征集,而不会造成太多信息损失。 有多种技术可以执行特征选择。 本章后面的部分将讨论其中的一些。 + +# 造型 + +在建模过程中,我们通常将数据特征提供给 ML 方法或算法并训练模型,通常是为了优化特定的成本函数,在大多数情况下,目的是减少错误并归纳从数据中学到的表示形式。 + +根据数据集和项目要求,我们应用一种或多种不同的机器学习技术的组合。 这些可以包括有监督的技术(例如分类或回归),无监督的技术(例如聚类),甚至是结合了不同技术的混合方法(如先前在 *ML 技术*部分中所讨论的)。 + +建模通常是一个迭代过程,我们经常利用多种算法或方法,并根据模型评估性能指标来选择最佳模型。 由于这是一本有关迁移学习的书,因此我们将在后续章节中主要基于深度学习构建模型,但是建模的基本原理与 ML 模型非常相似。 + +# 模型评估和调整 + +开发模型只是从数据中学习的一部分。 建模,评估和调整是迭代步骤,可帮助我们微调和选择性能最佳的模型。 + +# 模型评估 + +模型基本上是数据的通用表示形式,并且是用于学习该表示形式的基础算法。 因此,模型评估是针对某些标准评估构建模型以评估其性能的过程。 模型性能通常是定义为提供数值以帮助我们确定任何模型的有效性的函数。 通常,会根据这些评估指标对成本或损失函数进行优化以构建准确的模型。 + +根据所使用的建模技术,我们利用相关的评估指标。 对于有监督的方法,我们通常利用以下技术: + +* 根据模型预测与实际值创建混淆矩阵。 这涵盖了诸如**真阳性**( **TP** ),**假阳性**( **FP** ),**真阴性**( **TN** )和**假阴性**( **FN** )将其中一个类别视为正类别(通常是感兴趣的类别)。 +* 从混淆矩阵得出的度量标准,包括准确性(总体性能),精度(模型的预测能力),召回率(命中率)和 F1 分数(精确度和召回率的谐和平均值)。 +* **接收器操作员特征曲线**( **ROC** )和曲线( **AUC** )下的**面积,表示 AUC。** +* R 平方(确定系数),**均方根误差**( **RMSE** ),F 统计量,**赤池信息准则**( **AIC** ),以及专门用于回归模型的 p 值。 + +评估无监督方法(例如聚类)的流行指标包括: + +* 轮廓系数 +* 误差平方和 +* 同质性,完整性和 V 度量 +* 卡林斯基-哈拉巴兹指数 + +请注意,此列表描述了广泛使用的最受欢迎的度量标准,但绝不是模型评估度量标准的详尽列表。 + +交叉验证也是模型评估过程的重要方面,在该过程中,我们利用基于交叉验证策略的验证集通过调整模型的各种超参数来评估模型性能。 您可以将超参数视为可以用来调整模型以构建有效且性能更好的模型的旋钮。 当我们在随后的章节中使用大量的实际操作示例来评估模型时,这些评估技术的用法和细节将更加清晰。 + +# 偏差方差折衷 + +监督学习算法可帮助我们推断或学习从输入数据点到输出信号的映射。 该学习导致目标或学习的功能。 现在,在理想情况下,目标函数将学习输入变量和输出变量之间的精确映射。 不幸的是,没有理想。 + +正如在介绍监督学习算法时所讨论的那样,我们利用称为**训练数据集**的数据子集来学习目标函数,然后在称为**测试数据集**的另一个子集上测试性能。 由于该算法仅看到所有可能数据组合的子集,因此在预测输出和观察到的输出之间会出现错误。 这称为**总误差**或**预测误差**: + +*总误差=偏差误差+方差误差+不可约误差* + +不可减少的误差是由于噪声,我们对问题的框架,收集数据的方式等导致的固有误差。 顾名思义,这种错误是无法避免的,从算法的角度来看,我们几乎无能为力。 + +# 偏压 + +术语**偏差**是指由学习算法推断目标函数的基础假设。 高偏差表明该算法对目标函数有更多假设,而低偏差表明该假设较少。 + +由偏差引起的误差只是预期(或平均)预测值与实际观察值之间的差。 为了获得预测的平均值,我们多次重复学习步骤,然后取平均结果。 偏差误差有助于我们了解模型的概括性。 低偏差算法通常是非参数算法,例如决策树,SVM 等,而参数函数(例如线性和逻辑回归)的偏差很高。 + +# 方差 + +方差标志着模型对训练数据集的敏感性。 众所周知,学习阶段依赖于称为训练集的所有可能数据组合的一小部分。 因此,随着训练数据集的变化,方差误差捕获了模型估计值的变化。 + +低方差表明预测值的变化要少得多,因为基础训练数据集会发生变化,而高方差则指向另一个方向。 非参数算法(例如决策树)具有较高的方差,而参数算法(例如线性回归)的灵活性较差,因此方差较低。 + +# 交易 + +偏差方差折衷是同时减少监督学习算法的偏差和方差误差的问题,这阻止了目标函数泛化到训练数据点之外。 让我们看一下以下插图: + +![](img/bfa1f85c-82ab-4340-bbd0-8ca16ea2286a.png) + +偏差方差折衷 + +鼓励读者访问以下链接,以更好,更深入地了解偏差方差的折衷方法: [http://scott.fortmann-roe.com/docs/BiasVariance.html](http://scott.fortmann-roe.com/docs/BiasVariance.html) 和 [https://elitedatascience.com/bias-variance-tradeoff](https://elitedatascience.com/bias-variance-tradeoff) 。 + +考虑给我们给出一个问题陈述:g *等于一个人的身高,确定他/她的体重*。 我们还为训练数据集提供了相应的身高和体重值。 数据如下图所示: + +![](img/72d4d8c2-aaaa-4e37-bca5-cc30ce55657f.png) + +描绘身高体重数据集的图 + +请注意,这是一个说明重要概念的示例,在解决实际问题的后续章节中,我们将使用实际案例。 + +这是监督学习问题的一个实例,更多是回归问题的实例(请参阅为什么?)。 利用该训练数据集,我们的算法将必须学习目标函数,以找到不同个体的身高和体重之间的映射。 + +# 不合身 + +根据我们的算法,训练阶段可能会有不同的输出。 假设学习的目标函数如下图所示: + +![](img/f4a1030f-612c-461f-92cf-cf4f410be996.png) + +欠拟合模型 + +该惰性函数始终预测恒定的输出值。 由于目标函数无法学习数据的基础结构,因此导致**不适合**的情况。 欠拟合模型的预测性能较差。 + +# 过度拟合 + +训练阶段的另一个极端称为过拟合。 过度拟合图可以表示如下: + +![](img/f1c31eb8-3da3-4746-af69-1851a2bc27cc.png) + +过拟合模型 + +这显示了一个目标函数,可以完美地映射训练数据集中的每个数据点。 这就是众所周知的**模型过度拟合**。 在这种情况下,该算法试图了解包括噪声在内的确切数据特征,因此无法可靠地预测看不见的新数据点。 + +# 概括 + +欠拟合和过度拟合之间的最佳结合点是我们所说的**良好拟合**。 可以很好地概括给定问题的模型图如下: + +![](img/12c9ca72-544a-460a-8d0c-023b1eda7747.png) + +很好的概括 + +可以在看不见的数据点以及训练数据上表现良好的学习功能被称为**通用功能**。 因此,泛化指的是基于在训练阶段学到的概念,目标功能在看不见的数据上执行得如何。 上图描绘了很好的概括拟合。 + +# 模型调整 + +准备和评估模型与调整模型一样重要。 与为我们提供标准算法集的不同 ML 框架/库一起工作,我们几乎从来没有立即使用它们。 + +ML 算法具有不同的参数或旋钮,可以根据项目要求和不同的评估结果进行调整。 通过遍历超参数或元参数的不同设置来获得更好的结果,模型调整可以起作用。 超参数是高级抽象上的旋钮,它是在学习过程开始之前设置的。 + +这与模型级别参数不同,模型级别参数是在训练阶段期间学习的*。 因此,模型调整也称为**超参数优化**。* + +网格搜索,随机超参数搜索,贝叶斯优化等是执行模型调整的流行方法。 尽管模型调整非常重要,但过度调整可能会对学习过程产生不利影响。 在*偏差方差* *权衡*部分中讨论了与过度调整过程有关的一些问题。 + +# 部署和监控 + +一旦完成了模型开发,评估和调整,并进行了多次迭代以改善结果,那么模型部署的最后阶段就到了。 模型部署负责各个方面,例如模型持久性,通过不同的机制(例如 API 端点)将模型公开给其他应用程序,以及制定监视策略。 + +我们生活在一个瞬息万变的世界,每时每刻都在变化,关于数据和与用例相关的其他因素也是如此。 我们必须制定监控策略,例如定期报告,日志和测试,以检查解决方案的性能并在需要时进行更改。 + +机器学习管道与软件工程以及数据科学和机器学习一样重要。 我们简要概述并讨论了典型管道的不同组成部分。 根据特定的用例,我们修改了标准管道以适应需要,同时确保我们不会忽略已知的陷阱。 在接下来的部分中,我们将通过实际示例和代码片段更详细地了解典型 ML 管道的几个组件。 + +# 探索性数据分析 + +当我们开始任何 ML 项目时,EDA 就是我们执行的前几个任务之一。 正如在 *CRISP-DM* 一节中所讨论的那样,数据理解是发现有关数据的各种见解并更好地理解业务需求和上下文的重要步骤。 + +在本节中,我们将使用一个实际的数据集,并使用`pandas`作为我们的数据处理库以及`seaborn`进行可视化来执行 EDA。 Python 笔记本`game_of_thrones_eda.ipynb`中提供了完整的代码段和此分析的详细信息。 + +首先,我们导入所需的库并按照以下代码片段所示设置配置: + +```py +In [1]: import numpy as np + ...: import pandas as pd + ...: from collections import Counter + ...: + ...: # plotting + ...: import seaborn as sns + ...: import matplotlib.pyplot as plt + ...: + ...: # setting params + ...: params = {'legend.fontsize': 'x-large', + ...: 'figure.figsize': (30, 10), + ...: 'axes.labelsize': 'x-large', + ...: 'axes.titlesize':'x-large', + ...: 'xtick.labelsize':'x-large', + ...: 'ytick.labelsize':'x-large'} + ...: + ...: sns.set_style('whitegrid') + ...: sns.set_context('talk') + ...: + ...: plt.rcParams.update(params) +``` + +设置和要求到位后,我们可以开始关注数据。 正在考虑进行探索性分析的数据集是`battles.csv`文件,其中包含*权力游戏*(截至第 5 季)的所有主要战役。 + +*权力游戏*是有史以来最受欢迎的电视连续剧之一,是一部幻想小说,背景是在 *Westeros* 和 *Essos* 的虚构大陆上制作的,充满了许多情节 还有大量的角色都在为铁王座而战! 它是 George R. R. Martin 的 *A* *冰与火之歌*小说系列的改编。 作为一个受欢迎的系列,它吸引了许多人的注意,并且数据科学家也不排除在外。 该笔记本在 Myles O'Neill 增强的 Kaggle 数据集上显示了 EDA(更多详细信息: [https://www.kaggle.com/mylesoneill/game-of-thrones](https://www.kaggle.com/mylesoneill/game-of-thrones) )。 该数据集基于多个人收集并贡献的多个数据集的组合。 在此分析中,我们使用了`battles.csv`。 原始战斗数据由克里斯·阿尔邦(Chris Albon)提供; 更多详细信息,请参见 [https://github.com/chrisalbon/war_of_the_five_kings_dataset](https://github.com/chrisalbon/war_of_the_five_kings_dataset) 。 + +以下代码段使用`pandas`加载`battles.csv`文件: + +```py +In [2]: battles_df = pd.read_csv('battles.csv') +``` + +数据集如以下屏幕快照所示: + +![](img/de9875f7-8674-4f2f-909a-828daa8bd1c0.png) + +权力游戏的 Battles.csv 中的示例行 + +我们可以分别使用`pandas`实用程序`shape`,`dtypes`和`describe()`查看行的总数,每个属性的数据类型以及数字属性的常规统计信息。 我们有 38 场战斗的数据,其中 25 项属性描述了每一场。 + +让我们了解幻想世界多年来战斗的分布情况。 以下代码段绘制了此分布的条形图: + +```py +In [3]: sns.countplot(y='year',data=battles_df) + ...: plt.title('Battle Distribution over Years') + ...: plt.show() + +``` + +下图显示,在 **299** 年中,战斗次数最多,其次分别是 **300** 和 **298** : + +![](img/3a4330e8-8f0c-440a-aa0c-b0faf5f172d9.png) + +多年来的战斗分配 + +在这个幻想的土地上有不同的地区,在可以想象的每个地方都发生着战斗。 但是,有趣的是看看是否有任何首选区域。 以下代码段有助于我们准确地回答此问题: + +```py +In [4]: sns.countplot(x='region',data=battles_df) +...: plt.title('Battles by Regions') +...: plt.show() +``` + +以下图表可帮助我们确定**里弗兰兹**战斗最多,其次是**北部**和**西部**: + +![](img/be3c2da1-6403-4145-8e3f-bc0c2b09bfb6.png) + +地区战 + +还要注意的另一件有趣的事是,只有一场战役**超越了墙面**(扰流板警报:请稍后关注)。 + +我们可以使用不同的分组依据进行类似的分析,以了解例如主要死亡人数或每个地区的捕获数,等等。 + +我们继续前进,看看哪个国王攻击最多。 我们使用饼图将其可视化,以了解每个参与的国王所进行的战斗所占的百分比。 请注意,我们基于攻击国王进行此分析。 防御王也可以执行类似的分析。 以下代码段准备了一个饼图,以显示每个进攻国王的战斗份额: + +```py + In [5]: attacker_king = battles_df.attacker_king.value_counts() + ...: attacker_king.name='' # turn off annoying y-axis-label + ...: attacker_king.plot.pie(figsize=(6, 6),autopct='%.2f') +``` + +以下饼形图显示了每个进攻国王的战斗份额: + +![](img/d35c4569-5d5f-4fd3-8433-9e3b2ee1790f.png) + +每个进攻国王的战斗份额 + +韦斯特罗斯和埃索斯的土地充满危险,遍布敌人和威胁。 让我们稍微分析一下数据,以了解每位国王在多少次获胜者。 由于国王既可以保卫自己的土地,也可以争取权力,因此看到保卫和进攻的胜利也很有趣。 以下代码段有助于我们准备堆积的条形图,以分析每位国王的进攻和防守胜利: + +```py +In [6] : attack_winners = battles_df[battles_df. + ...: attacker_outcome=='win'] + ...: ['attacker_king']. + ...: value_counts(). + ...: reset_index() + ...: + ...: attack_winners.rename( + ...: columns={'index':'king', + ...: 'attacker_king':'wins'}, + ...: inplace=True) + ...: + ...: attack_winners.loc[:,'win_type'] = 'attack' + ...: + ...: defend_winners = battles_df[battles_df. + ...: attacker_outcome=='loss'] + ...: ['defender_king']. + ...: value_counts(). + ...: reset_index() + ...: defend_winners.rename( + ...: columns={'index':'king', + ...: 'defender_king':'wins'}, + ...: inplace=True) + ...: + ...: defend_winners.loc[:,'win_type'] = 'defend' + ...: + ...: + ...: sns.barplot(x="king", + ...: y="wins", + ...: hue="win_type", + ...: data=pd.concat([attack_winners, + ...: defend_winners])) + ...: plt.title('Kings and Their Wins') + ...: plt.ylabel('wins') + ...: plt.xlabel('king') + ...: plt.show() +``` + +前面的代码段计算出攻击时每位国王的获胜次数,然后计算出防守时每位国王的获胜次数。 然后,我们将两个结果合并,并使用堆叠的 barplot 绘制相同的结果。 结果显示在下图中: + +![](img/e6b49a78-6a3d-4f24-9cb0-ab2b86e2591b.png) + +每位国王获胜的次数 + +上图清楚地表明,**马拉松**男孩在进攻和防守中获胜的次数最多。 到目前为止,他们似乎很幸运。 **Robb Stark** 是第二成功的国王,当然红色婚礼发生了。 + +数据集还包含描述所涉及房屋的数量,战斗指挥官和军队规模的属性。 我们可以进行类似且更深入的分析,以更好地理解战斗。 我们鼓励读者尝试其中一些作为练习,并检查 Python Notebook 中的更多指针。 + +在结束本节之前,让我们尝试确定铁王座之战中的敌人。 尽管粉丝已经对此有所了解,但让我们看看数据对此有何评论。 以下代码段可帮助我们回答此问题: + +```py +In [7]: temp_df = battles_df.dropna( + ...: subset = ["attacker_king", + ...: "defender_king"])[ + ...: ["attacker_king", + ...: "defender_king"] + ...: ] + ...: + ...: archenemy_df = pd.DataFrame( + ...: list(Counter( + ...: [tuple(set(king_pair)) + ...: for king_pair in temp_df.values + ...: if len(set(king_pair))>1]). + ...: items()), + ...: columns=['king_pair', + ...: 'battle_count']) + ...: + ...: archenemy_df['versus_text'] = archenemy_df. + ...: apply( + ...: lambda row: + ...: '{} Vs {}'.format( + ...: row[ + ...: 'king_pair' + ...: ][0], + ...: row[ + ...: 'king_pair' + ...: ][1]), + ...: axis=1) + ...: archenemy_df.sort_values('battle_count', + ...: inplace=True, + ...: ascending=False) + ...: + ...: + ...: archenemy_df[['versus_text', + ...: 'battle_count']].set_index('versus_text', + ...: inplace=True) + ...: sns.barplot(data=archenemy_df, + ...: x='versus_text', + ...: y='battle_count') + ...: plt.xticks(rotation=45) + ...: plt.xlabel('Archenemies') + ...: plt.ylabel('Number of Battles') + ...: plt.title('Archenemies') + ...: plt.show() +``` + +我们首先准备一个临时数据帧,并删除所有未列出攻击者或捍卫者国王姓名的战斗。 有了干净的数据框后,我们将遍历每一行并计算每对战斗的战斗次数。 我们忽略了战斗是国王自己的军队(`if len(set(king_pair))>1`)发生的情况。 然后,我们将结果简单地绘制成条形图,如下所示: + +![](img/4c391b2d-6a89-4c72-93f0-458c55e7e3b6.png) + +权力游戏中的大敌 + +我们看到数据集证实了直觉。 **Robb Stark** 和 **Joffrey Baratheon** 已经进行了 19 场战斗,其他两对则进行了五场或更少的战斗。 + +本节中共享的分析和可视化效果是对数据集可以完成的工作的一瞥。 仅从该数据集中可以提取出更多的模式和见解。 + +EDA 是一种非常强大的机制,可用于在进入 ML 的其他阶段之前详细了解数据集。 在接下来的章节中,在进入建模,调整,评估和部署阶段之前,我们将定期执行 EDA,以帮助我们理解业务问题以及数据集。 + +# 特征提取与工程 + +数据准备是任何 ML 项目中最长,最复杂的阶段。 在讨论 CRISP-DM 模型时,强调了同样的道理,在该模型中,我们提到了数据准备阶段如何占用 ML 项目中总时间的 60-70%。 + +对原始数据集进行预处理和处理后,下一步就是使其可用于 ML 算法。 特征提取是从原始属性派生特征的过程。 例如,在处理图像数据时进行特征提取是指从原始像素级数据中提取红色,蓝色和绿色通道信息作为特征。 + +同样,特征工程指的是使用数学变换从现有特征中推导其他特征的过程。 例如,功能工程将帮助我们从一个人的月收入中得出一个功能,例如年收入(基于用例要求)。 由于特征提取和工程设计都可以帮助我们将原始数据集转换为可用形式,因此 ML 实践者可以互换使用这些术语。 + +# 特色工程策略 + +将原始数据集(后期清理和整理)转换为可以由 ML 算法使用的功能的过程是领域知识,用例需求和特定技术的组合。 因此,要素描述了基础数据的各种表示形式,并且是要素工程过程的结果。 + +由于特征工程将原始数据转换为自身的有用表示形式,因此根据手头的数据类型,可以使用各种标准技术和策略。 在本节中,我们将讨论其中一些策略,简要介绍结构化和非结构化数据。 + +# 处理数值数据 + +数值数据通常以整数或浮点数的形式在数据集中可用,并且通常称为**连续** **数值数据**,通常是 ML 友好数据类型。 友好地说,我们指的是可以直接在大多数 ML 算法中摄取数字数据的事实。 但是,这并不意味着数字数据不需要其他处理和特征工程步骤。 + +有多种技术可以从数值数据中提取和工程化特征。 让我们看一下本节中的一些技术: + +* **原始度量**:这些数据属性或特征可以直接以其原始或本机格式使用,因为它们出现在数据集中而无需任何其他处理。 示例可以是年龄,身高或体重(只要数据分布不太偏斜!)。 +* **计数**:在某些情况下,诸如计数和频率之类的数字功能也很有用,以描绘重要的细节。 例如,信用卡欺诈事件的发生次数,歌曲收听次数,设备事件发生的次数等等。 +* **二值化**:我们经常可能想对出现或特征进行二值化,特别是仅指示是否存在特定项或属性(通常以 1 表示)或不存在(以 0 表示)。 这在构建推荐系统等场景中很有用。 +* **合并**:此技术通常将来自分析中的任何要素或属性的连续数值分组或分组为离散的分组,以使每个分组都覆盖特定的数值范围。 一旦获得了这些离散的 bin,我们就可以选择在其上进一步应用基于分类数据的特征工程。 存在各种装箱策略,例如固定宽度装箱和自适应装箱。 + +笔记本`feature_engineering_numerical_and_categorical_data.ipynb`中提供了代码片段,以更好地理解数字数据的功能工程。 + +# 处理分类数据 + +常见的另一类重要数据是分类数据。 分类要素具有离散值,这些离散值属于一组有限的类。 这些类可以表示为文本或数字。 根据分类的顺序,分类特征分别称为**标称**和**标称**。 + +名义特征是具有有限值集合但没有任何自然顺序的那些分类特征。 例如,天气季节,电影类型等都是标称特征。 具有有限类集并具有自然顺序的分类特征称为**序数特征**。 例如,星期几,着装大小等都是常规的。 + +通常,要素工程中的任何标准工作流程都涉及将这些分类值转换为数字标签的某种形式,然后在这些值上应用某种编码方案。 流行的编码方案简要介绍如下: + +* **一键编码**:此策略为分类属性创建 *n* 个二进制值列,假设存在 *n* 个不同类别 +* **虚拟编码**:此策略为分类属性创建 *n-1* 个二进制值列,假定存在 *n* 个不同类别 +* **特征散列**:如果我们使用散列函数将多个特征添加到单个容器或存储桶(新特征)中,则可以利用此策略,当我们具有大量特征时通常会使用 + +笔记本`feature_engineering_numerical_and_categorical_data.ipynb`中提供了代码片段,以更好地理解分类数据的特征工程。 + +# 处理图像数据 + +图像或视觉数据是丰富的数据源,可以使用 ML 算法和深度学习解决几个用例。 图像数据提出了很多挑战,需要经过仔细的预处理和转换,然后才能被任何算法使用。 对图像数据执行特征工程的一些最常见方法如下: + +* **利用元数据信息或 EXIF 数据**:诸如图像创建日期,修改日期,尺寸,压缩格式,用于捕获图像的设备,分辨率,焦距等属性。 +* **像素和通道信息**:每个图像都可以视为像素值矩阵或([ *m* , *n* , *c* )矩阵 其中 *m* 代表行数, *n* 代表列数, *c* 指向颜色通道(例如 R,G 和 B)。 然后可以根据算法和用例的要求将这种矩阵转换为不同的形状。 +* **像素强度**:有时很难处理具有多种颜色通道的彩色图像。 基于像素强度的特征提取依赖于基于强度的像素合并,而不是利用原始像素级别的值。 +* **边缘检测**:可以利用相邻像素之间对比度和亮度的急剧变化来识别物体边缘。 有不同的算法可用于边缘检测。 +* **对象检测**:我们采用边缘检测的概念并将其扩展到对象检测,然后将识别出的对象边界用作有用的功能。 同样,可以基于可用图像数据的类型来利用不同的算法。 + +# 基于深度学习的自动特征提取 + +到目前为止讨论的图像数据和其他类型的特征提取方法需要大量时间,精力和领域知识。 这种特征提取有其优点和局限性。 + +最近,人们已经研究了深度学习,特别是**卷积神经网络**( **CNN** ),并将其用作自动特征提取器。 CNN 是针对图像数据进行优化的深度神经网络的特例。 卷积层是任何 CNN 的核心,它们基本上会在图像的高度和宽度上应用滑动滤镜。 像素值与这些滤镜的点积会生成跨多个时期学习的激活图。 在每个层次上,这些卷积层都有助于提取特定特征,例如边缘,纹理,角等。 + +深度学习和 CNN 还有很多,但是为了简单起见,让我们假设 CNN 在每一层都可以帮助我们自动提取不同的低级和高级功能。 反过来,这使我们免于手动执行特征提取。 我们将在接下来的章节中更详细地研究 CNN,并了解它们如何帮助我们自动提取特征。 + +# 处理文本数据 + +数值和分类特征就是我们所谓的结构化数据类型。 它们在 ML 工作流中更易于处理和利用。 文本数据是非结构化信息的重要来源之一,同样重要。 文本数据提出了与句法理解,语义,格式和内容有关的多个挑战。 文本数据还提出了转换为数字形式的问题,然后才能被 ML 算法使用。 因此,在进行文本数据的特征工程之前,需要进行严格的预处理和清理步骤。 + +# 文字预处理 + +在进行任何特征提取/工程设计之前,文本数据需要仔细而勤奋的预处理。 预处理文本数据涉及多个步骤。 以下是一些最广泛使用的文本数据预处理步骤的列表: + +* 代币化 +* 下套管 +* 删除特殊字符 +* 收缩扩张 +* 停用词删除 +* 拼写更正 +* 词干和词条去除 + +在与用例有关的章节中,我们将详细介绍大多数技术。 为了更好地理解,读者可以参考 *Python 实用机器学习的*第 4 章*和*第 7 章*,* Sarkar 及其合著者,Springer,2017 年。 + +# 特征工程 + +通过上一节中提到的方法对文本数据进行正确处理后,我们就可以利用以下一些技术来进行特征提取和转换为数值形式。 Jupyter Notebook `feature_engineering_text_data.ipynb`中提供了可更好地理解文本数据功能的代码片段: + +* **词袋模型**:这是迄今为止最简单的文本数据矢量化技术。 在此技术中,每个文档都表示为 *N* 维度上的向量,其中 *N* 表示预处理语料库中所有可能的单词,向量的每个组成部分要么表示存在 单词或其频率。 +* **TF-IDF model**:The bag-of-words model works under very simplistic assumptions and at certain times leads to various issues. One of the most common issues is related to some words overshadowing the rest of the words due to very high frequency, as the bag-of-words model utilizes absolute frequencies to vectorize. The **Term Frequency-Inverse Document Frequency** (**TF-IDF**) model mitigates this issue by scaling/normalizing the absolute frequencies. Mathematically, the model is defined as follows: + + *tfidf(w,D)= tf(W,D)* idf(w,D)* + + 在这里, *tfidf(w,D)*表示文档 *D* , *tf(w,D)中每个单词 *w* 的 TF-IDF 分数[ “ HTG7”是文档 *D* 和 *idf(w,D)中单词 *w* 的频率,*表示文档的逆频率,以总文档的对数转换计算 语料库 *C* 中的词数除以出现 *w* 的文档的频率。* + +除了单词袋和 TF-IDF 外,还有其他转换,例如 N-gram 袋,以及单词嵌入,例如 Word2vec,GloVe 等。 我们将在后续章节中详细介绍其中的几个。 + +# 功能选择 + +特征提取和工程设计的过程可帮助我们从基础数据集中提取特征并生成特征。 在某些情况下,这会导致大量输入要处理的算法。 在这种情况下,怀疑输入中的许多功能可能是多余的,并可能导致复杂的模型甚至过度拟合。 特征选择是从可用/生成的完整特征集中识别代表性特征的过程。 预期所选特征集将包含所需信息,以使算法能够解决给定任务而不会遇到处理,复杂性和过度拟合的问题。 功能选择还有助于更好地理解建模过程中使用的数据,并加快处理速度。 + +特征选择方法大致可分为以下三类: + +* **过滤方法**:顾名思义,这些方法可帮助我们根据统计得分对要素进行排名。 然后,我们选择这些功能的子集。 这些方法通常与模型输出无关,而是独立地评估特征。 基于阈值的技术和统计检验(例如相关系数和卡方检验)是一些受欢迎的选择。 +* **包装器方法**:这些方法对特征子集的不同组合的性能进行比较搜索,然后帮助我们选择性能最佳的子集。 向后选择和正向消除是用于特征选择的两种流行的包装方法。 +* **嵌入式方法**:这些方法通过了解哪个功能子集将是最好的,从而提供了前面两种方法中的最好方法。 正则化和基于树的方法是流行的选择。 + +特征选择是构建 ML 系统过程中的重要方面。 如果不谨慎处理,它也是造成系统偏差的主要来源之一。 读者应注意,应该使用与训练数据集分开的数据集来进行特征选择。 将训练数据集用于特征选择将始终导致过度拟合,而将测试集用于特征选择则会高估模型的性能。 + +最受欢迎的库提供了多种功能选择技术。 诸如`scikit-learn`之类的库提供了开箱即用的这些方法。 在后面的章节中,我们将看到并利用其中的许多内容。 + +# 摘要 + +在任何旅程中,了解概念和技术的牢固基础和共同基础都非常重要。 通过本章有关机器学习基础知识的章节,我们试图实现这一目标。 在开始学习深度学习,迁移学习和更高级的概念之前,必须为 ML 概念奠定坚实的基础。 在本章中,我们涵盖了相当多的基础,并提供了更详细地研究概念的重要指示。 + +我们通过理解机器学习为何如此重要以及它是一个完全不同的范例来开始本章。 我们简要讨论了 AI,机器学习和深度学习之间的关系。 然后,本章继续介绍了不同的机器学习技术,例如有监督,无监督和强化学习。 我们详细讨论了通常使用哪些不同的监督和非监督方法。 + +本章还简要介绍了用于 ML 项目工作流程的 CRISP-DM 模型以及 ML 管道。 我们还讨论了《权力的游戏》幻想世界中战斗数据集的 EDA,以应用不同的概念并了解 EDA 的重要性。 在本章的最后,介绍了特征提取和工程以及特征选择。 + +在接下来的章节中,我们将以这些概念为基础,并最终在涉及不同实际使用案例的章节中应用所学知识。 欢迎登机! \ No newline at end of file diff --git a/docs/handson-tl-py/10.md b/docs/handson-tl-py/10.md new file mode 100644 index 0000000000000000000000000000000000000000..bfbdb0ae4a2d47e9173647654689a6dde2cb4426 --- /dev/null +++ b/docs/handson-tl-py/10.md @@ -0,0 +1,1201 @@ +# 自动图像字幕生成器 + +在前面的章节中,我们研究了一些案例研究,这些案例研究将转移学习应用于计算机视觉以及**自然语言处理**( **NLP** )中的问题。 但是,这些都是它们各自特定领域中的问题。 在本章中,我们将专注于构建将这两个流行领域(计算机视觉和 NLP)结合在一起的智能系统。 更具体地说,我们将专注于构建与机器翻译相结合的对象识别系统,以构建自动图像字幕生成器。 + +图像字幕的想法并不是什么新鲜事物。 通常,存在于各种媒体资源(例如书籍,论文或社交媒体)中的任何图像通常都需要加上适当的文字说明,以获取更好的含义和上下文。 使这项任务变得艰巨的是,图像标题通常是由一个或多个句子组成的自由流动的自然语言。 因此,由于用于图像标题的文本数据的非结构化性质,这不是传统的图像分类问题。 + +可以通过结合使用计算机视觉领域专家的预训练模型(例如**视觉几何组**( **VGG** )和 Inception 以及序列模型(例如 作为**递归神经网络**( **RNN** )或**长短期记忆**( **LSTM** ),以生成单词序列以形成适当的单词 图片说明。 在本章中,我们将探索一种有趣的方法来构建自动图像字幕或场景识别系统。 + +我们将涵盖构建此系统的以下主要方面,该系统由深度学习和迁移学习提供支持: + +* 了解图像字幕 +* 制定目标 +* 了解数据 +* 自动图像字幕的方法 +* 带有转移学习的图像特征提取 +* 为我们的字幕建立词汇 +* 构建图像标题数据集生成器 +* 建立我们的图像语言编解码器深度学习模型 +* 训练我们的图像字幕深度学习模型 +* 动作中的自动图像字幕 + +我们将涵盖计算机视觉和 NLP 的基本概念,以构建我们的自动图像标题生成器。 我们将深入研究适合的深度学习架构,并结合迁移学习,以在流行且易于使用的图像数据集之上实施该系统。 我们还将展示如何在新的照片和场景上构建和测试我们的自动图像标题生成器。 您可以在 GitHub 存储库中的 [https://github.com/dipanjanS/hands-on-transfer-learning-with-python](https://github.com/dipanjanS/hands-on-transfer-learning-with-python) 的 GitHub 存储库的`Chapter 11`文件夹中快速阅读本章的代码。 可以根据需要参考本章。 我们还将在那里发布一些奖金示例。 + +# 了解图像字幕 + +到目前为止,您应该了解图像字幕的意义和含义。 该任务可以简单地定义为为任何图像编写和记录自由流动的自然文本描述。 通常用于描述图像中的各种场景或事件。 这也通常称为**场景识别**。 让我们看下面的例子: + +![](img/08624886-c3d1-4202-a1a6-9121e0ab4c19.jpg) + +看着这个场景,合适的标题或描述是什么? 以下是对场景的所有有效描述: + +* 越野摩托车手在土丘上 +* 一个在山上的自行车空中的家伙 +* 一辆越野车车手正在一条肮脏的道路上快速移动 +* 骑自行车的人在空中骑黑摩托车 + +您会看到所有这些标题都是有效的并且相似,但是使用不同的词来传达相同的含义。 这就是为什么自动生成图像标题并非易事的原因。 + +实际上,流行论文*显示和讲述:神经图像字幕生成器*,Vinyals 及其合作者,2015 年( [https://arxiv.org/abs /1411.4555](https://arxiv.org/abs/1411.4555) )描述了图像字幕,从中我们汲取了构建此系统的灵感: + +*自动描述图像的内容是连接计算机视觉和自然语言处理的人工智能的基本问题。* + +对于一个人来说,只需瞥一眼照片或图像几秒钟就足以生成基于自然语言的字幕。 但是,由于大多数计算机视觉问题都集中在识别和分类问题上,因此使**人工智能**( **AI** )执行此任务极具挑战性。 就复杂性而言,这是有关核心计算机视觉问题的一些主要任务: + +* **图像分类和识别**:这涉及经典的有监督学习问题,其中主要目标是基于几个预定义的类类别(通常称为**类标签**)将图像分配给特定类别 。 流行的 ImageNet 竞赛就是这样一项任务。 +* **图像注释**:稍微复杂一点的任务,我们尝试使用图像中各个实体的描述来注释图像。 通常,这涉及图像中特定部分或区域的类别,甚至是基于自然语言的文本描述。 +* **图像标题或场景识别**:我们尝试使用准确的基于自然语言的文字描述来描述图像的另一项复杂任务。 这是本章重点关注的领域。 + +图像字幕的任务不是什么新鲜事。 已有多种利用技术的现有方法,例如将图像中各个实体的文本描述缝合在一起以形成描述,甚至使用基于模板的文本生成技术。 但是,对于该任务,使用深度学习是一种更强大,更有效的方法。 + +# 制定目标 + +我们实际案例研究的主要目标是图像字幕或场景识别。 在一定程度上,这是一个监督学习问题,而不是传统的分类问题。 在这里,我们将处理一个称为`Flickr8K`的图像数据集,其中包含图像或场景的样本以及描述它们的相应自然语言标题。 这个想法是建立一个可以从这些图像中学习并自动开始为图像添加字幕的系统。 + +如前所述,传统的图像分类系统通常将图像分类或分类为预定义的类。 在前面的章节中,我们已经构建了这样的系统。 但是,图像字幕系统的输出通常是形成自然语言文本描述的单词序列; 这比传统的监督分类系统更加困难。 + +我们仍将监督模型训练的性质,因为我们将必须基于训练图像数据及其相应的字幕说明来构建模型。 但是,建立模型的方法会略有不同。 我们将利用迁移学习和深度学习中的概念照常构建此系统。 更具体地说,我们将结合使用**深层卷积神经网络**( **DCNNs** )和顺序模型。 + +# 了解数据 + +让我们看一下将用于构建模型的数据。 为简单起见,我们将使用`Flickr8K`数据集。 该数据集包括从流行的图像共享网站 Flickr 获得的图像。 要下载数据集,可以通过填写以下表格来请求它:伊利诺伊大学计算机科学系的 [https://forms.illinois.edu/sec/1713398](https://forms.illinois.edu/sec/1713398) ,您应该获取 下载电子邮件中的链接。 + +要查看与每个图像有关的详细信息,可以访问其网站 [http://nlp.cs.illinois.edu/HockenmaierGroup/8k-pictures.html](http://nlp.cs.illinois.edu/HockenmaierGroup/8k-pictures.html) ,其中讨论了每个图像及其图像。 源,以及每个图像的五个基于文本的标题。 通常,任何样本图像都将具有类似于以下内容的标题: + +![](img/37049eb5-b79c-43bf-b8b1-fceb07a6697c.png) + +您可以清楚地看到图像及其相应的标题。 很明显,所有标题都试图描述相同的图像或场景,但是它可能专注于图像的特定和不同方面,这使自动化成为一项艰巨的任务。 我们还建议读者查看*将图像描述作为排名任务:数据,模型和评估指标*,Micah Hodosh 等人,IJCAI 2015( [https://pdfs.semanticscholar .org / f126 / ec304cdad464f6248ac7f73a186ca26db526.pdf](https://pdfs.semanticscholar.org/f126/ec304cdad464f6248ac7f73a186ca26db526.pdf) )。 + +单击下载链接时,将获得两个文件: + +* `Flickr8k_Dataset.zip`:所有原始图像和照片的 1 GB ZIP 存档 +* `Flickr8k_text.zip`:3 MB 的 ZIP 存档,其中包含照片的所有自然语言文字说明,这些文字说明为标题 + +`Flickr_8k.devImages.txt`,`lickr_8k.trainImages.txt`和`Flickr_8k.testImages.txt`文件分别包含 6,000、1,000 和 1,000 个图像的文件名。 我们将合并`dev`和`train`图像,以构建包含 7,000 张图像的训练数据集,并使用包含 1,000 张图像的测试数据集进行评估。 每个图像都有五个不同但相似的标题,可在`Flickr8k.token.txt`文件中找到。 + +# 自动图像字幕的方法 + +现在,我们将讨论构建自动图像字幕系统的方法。 正如我之前提到的,我们的方法将利用基于深度神经网络的方法以及将学习转移到图像字幕的方法。 这得益于流行论文 *Show and Tell:神经图像字幕生成器*,Oriol Vinyals 等人,2015( [https://arxiv.org/abs/1411.4555](https://arxiv.org/abs/1411.4555) ) 。 我们将在概念上概述我们的方法,然后将其转换为将用于构建自动图像字幕系统的实用方法。 让我们开始吧! + +# 概念方法 + +成功的图像字幕系统需要一种将给定图像转换为单词序列的方法。 为了从图像中提取正确和相关的特征,我们可以利用 DCNN,再结合递归神经网络模型(例如 RNN 或 LSTM),我们可以构建混合生成模型以开始生成单词序列作为标题,给定 源图像。 + +因此,从概念上讲,这个想法是建立一个混合模型,该模型可以将源图像 *I* 作为输入,并可以进行训练以使可能性最大, *P(S | I),*,这样 *S* 是单词序列的输出,这是我们的目标输出,可以由 *S = {S 1 ,S 2 表示 ,...,S n }* ,这样每个单词 *S w* 都来自给定的词典,这就是我们的词汇。 该标题 *S* 应该能够对输入图像给出恰当的描述。 + +神经机器翻译是构建这样一个系统的绝佳灵感。 通常在语言模型中用于语言翻译,模型体系结构涉及使用 RNN 或 LSTM 构建的编码器-解码器体系结构。 通常,编码器涉及一个 LSTM 模型,该模型从源语言中读取输入语句并将其转换为密集的定长向量。 然后将其用作解码器 LSTM 模型的初始隐藏状态,最终以目标语言生成输出语句。 + +对于图像字幕,我们将利用类似的策略,其中处理输入的编码器将利用 DCNN 模型,因为我们的源数据是图像。 到目前为止,我们已经看到了基于 CNN 的模型在从图像中进行有效且丰富的特征提取的优势。 因此,源图像数据将转换为密集数字固定长度向量。 通常,利用转移学习方法的预训练模型将是最有效的方法。 此向量将用作我们的解码器 LSTM 模型的输入,该模型将生成字幕说明(如单词序列)。 从原始论文中汲取灵感,可以用数学方式表示要最大化的目标,如下所示: + +![](img/4e95a622-a125-4518-9d81-f0de283fe145.png) + +在此,Θ表示模型参数, *I* 表示输入图像, *S* 是其相应的由单词序列组成的标题描述。 考虑到长度为 *N* 的字幕说明,表示总共 *N* 个字,我们可以对 *{S 0 ,S 1 ,...,S N }* ,使用链式规则,如下所示: + +![](img/ea7fa58f-ba10-4924-a55c-5875ee0b307b.png) + +因此,在模型训练期间,我们有一对( *I* , *S* )图像标题作为输入,其思想是针对上一个方程式优化对数概率的总和 使用有效算法(例如随机梯度下降)来完整训练数据。 考虑到前面公式的 RHS 中的项序列,基于 RNN 的模型是合适的选择,这样,直到 *t-1* 的可变单词数依次由存储状态 *h 表示 t* 。 根据先前的 *t-1* 状态和输入对(图像和下一个字) *x t* ,使用以下命令在每个步骤中按以下步骤更新此内容: 非线性函数`f(...)`: + +*h t + 1 = f(h t ,x t )* + +通常, *x t* 代表我们的图像特征和文字,它们是我们的输入。 对于图像功能,我们利用了前面提到的 DCNN。 对于函数 *f* ,我们选择使用 LSTM,因为它们在处理消失和探索梯度等问题方面非常有效,这已在本书的初始章节中进行了讨论。 考虑到 LSTM 存储器块的简要介绍,让我们参考 *Show and Tell* 研究论文中的下图: + +![](img/78f9f35a-5341-47c2-8e0d-2759083c43a7.png) + +存储块包含 LSTM 单元 **c** ,该单元由输入,输出和忘记门控制。 单元 *c* 将根据输入对每个时间步的知识进行编码,直到先前的时间步为止。 如果门是 *1* 或 *0* ,则这三个门是可以相乘的层,以保持或拒绝来自门控层的值。 循环连接在上图中以蓝色显示。 我们通常在模型中有多个 LSTM,并且在时间 *t -1* 的输出 *m t-1* 在时间被馈送到下一个 LSTM。 *t* 。 因此,使用以下三个时间,将在时间 *t-1* 处的输出 *m t-1* 反馈到存储块。 我们前面讨论过的门。 实际的单元格值也使用“忘记门”反馈。 通常将时间 *t* 处的存储器输出 *m t* 输出到 softmax 以预测下一个单词。 + +这通常是从输出门 *o t* 和当前单元状态 *c t* 获得的。 下图中描述了其中的一些定义和操作以及必要的方程式: + +![](img/dc8b0dfb-4929-42ec-ab41-196bf28b0bbe.png) + +在这里,![](img/7e114bcc-d207-47f4-a6d0-7318698691f2.png)是乘积运算符,尤其用于当前的门状态和值。 *W* 矩阵是网络中的可训练参数。 这些门有助于解决诸如爆炸和消失梯度的问题。 网络中的非线性是由我们的常规 S 型![](img/368145b5-993b-4ece-acc2-74dfb213a8bc.png)和双曲正切 *h* 函数引入的。 如前所述,内存输出 *m t * 被馈送到 softmax 以预测下一个单词,其中输出是所有单词上的概率分布。 + +因此,基于此知识,您可以考虑基于 LSTM 的序列模型需要与必要的词嵌入层和基于 CNN 的模型结合,以从源图像生成密集特征。 因此,LSTM 模型的目标是根据预测的所有先前单词以及输入图像(由我们先前的 *p(S t | I,S 0 ,S 1 ,...,S t-1 )*。 为了简化 LSTM 中的循环连接,我们可以以展开形式来表示它,其中我们代表一系列 LSTM,它们共享下图所示的相同参数: + +![](img/25803aa5-92dd-4e45-9711-a1ef2878d940.png) + +从上图可以明显看出,基于展开的 LSTM 体系结构,循环连接由蓝色水平箭头表示,并已转换为前馈连接。 同样,很明显,在时间 *t-1* 处 LSTM 的输出 *m t-1* 在时间 *t 被馈送到下一个 LSTM。* 等。 将源输入图像视为 *I* ,将字幕视为 *S = {S 0 ,S 1 ,...,S N }* 下图描述了先前描述的展开架构中涉及的主要操作: + +![](img/f4d3d071-bd12-4d75-99c3-f51d640b329d.png) + +在这里,标题中的每个文本单词都由一个热门矢量 *S t* 表示,因此其尺寸等于我们词汇量(唯一的单词)。 另外要注意的一点是,我们为 *S 0* 设置了特殊的标记或分隔符,分别由``和 *S N 表示[* ,我们用``来表示字幕的开头和结尾。 这有助于 LSTM 理解何时完全生成了字幕。 + +输入图像 *I* 输入到我们的 DCNN 模型中,该模型生成密集特征向量,并将基于嵌入层的单词转换为密集单词嵌入 *W * 。 因此,要最小化的整体损失函数是每个步骤右词的对数似然比,如以下等式所示: + +![](img/e6a2071b-06ad-4962-91d1-2a66bf08c6e0.png) + +因此,在模型训练期间,考虑模型中的所有参数(包括 DCNN,LSTM 和嵌入),可以将这种损失最小化。 现在让我们看一下如何将其付诸实践。 + +# 实用的实践方法 + +现在我们知道了可用于构建成功的图像字幕生成器的基本概念和理论,下面让我们看一下需要动手实践来解决此问题的主要构建块。 基于图像字幕的主要操作,要构建模型,我们将需要以下主要组件: + +* 图像特征提取器—带转移学习的 DCNN 模型 +* 文字字幕生成器-使用 LSTM 的基于序列的语言模型 +* 编解码器型号 + +在为字幕生成系统实现它们之前,让我们简要介绍一下这三个组件。 + +# 图像特征提取器–具有转移学习功能的 DCNN 模型 + +我们系统的主要输入之一是源图像或照片。 我们都知道,**机器学习**( **ML** )或深度学习模型不能仅使用原始图像。 我们需要进行一些处理,还需要从图像中提取相关特征,然后将这些特征用于识别和分类等任务。 + +图像特征提取器本质上应该接收输入图像,从中提取丰富的层次特征表示,并以固定长度的密集矢量的形式表示输出。 我们已经看到了 DCNN 在处理计算机视觉任务方面的强大功能。 在这里,我们将通过使用预训练的 VGG-16 模型作为特征提取器来从所有图像中提取瓶颈特征,从而充分利用迁移学习的力量。 就像快速刷新一样,下图显示了 VGG-16 模型: + +![](img/9c6d795e-28b3-449a-9a67-10405cbce1e1.png) + +为了进行特征提取,我们将删除模型的顶部,即 softmax 层,并使用其余的层从输入图像中获取密集的特征向量。 这通常是编码过程的一部分,输出被馈送到产生字幕的解码器中。 + +# 文字字幕生成器–具有 LSTM 的基于序列的语言模型 + +如果传统的基于序列的语言模型知道序列中已经存在的先前单词,则它将预测下一个可能的单词。 对于我们的图像字幕问题,如上一节所述,基于 DCNN 模型的功能和字幕序列中已经生成的单词,LSTM 模型应该能够在每个时间步长预测我们字幕中的下一个可能单词 。 + +嵌入层用于为字幕数据字典或词汇表中的每个唯一单词生成单词嵌入,通常将其作为 LSTM 模型(解码器的一部分)的输入,以根据以下内容在我们的字幕中生成下一个可能的单词 图像特征和先前的词序。 想法是最终生成一系列单词,这些单词一起在描述输入图像时最有意义。 + +# 编解码器型号 + +这是将前面两个组件联系在一起的模型架构。 它最初是在神经机器翻译方面取得的巨大成功,通常您将一种语言的单词输入编码器,而解码器则输出另一种语言的单词。 好处是,使用单个端到端体系结构,您可以连接这两个组件并解决问题,而不必尝试构建单独的和断开的模型来解决一个问题。 + +DCNN 模型通常形成编码器,该编码器将源输入图像编码为固定长度的密集向量,然后由基于 LSTM 的序列模型将其解码为单词序列,从而为我们提供了所需的标题。 同样,如前所述,必须训练该模型以使给定输入图像的字幕文本的可能性最大化。 为了进行改进,您可以考虑将详细信息添加到此模型中,作为将来范围的一部分。 + +现在,让我们使用这种方法来实现我们的自动图像标题生成器。 + +# 带有转移学习的图像特征提取 + +我们模型的第一步是利用预训练的 DCNN 模型,使用转移学习的原理从源图像中提取正确的特征。 为简单起见,我们不会对 VGG-16 模型进行微调或将其连接到模型的其余部分。 我们将事先从所有图像中提取瓶颈特征,以加快以后的训练速度,因为使用多个 LSTM 构建序列模型即使在 GPU 上也需要大量的训练时间,我们很快就会看到。 + +首先,我们将从源数据集中的`Flickr8k_text`文件夹中加载所有源图像文件名及其相应的标题。 同样,我们将把`dev`和`train`数据集图像组合在一起,正如我们之前提到的: + +```py +import pandas as pd +import numpy as np + +# read train image file names +with open('../Flickr8k_text/Flickr_8k.trainImages.txt','r') as tr_imgs: + train_imgs = tr_imgs.read().splitlines() + +# read dev image file names +with open('../Flickr8k_text/Flickr_8k.devImages.txt','r') as dv_imgs: + dev_imgs = dv_imgs.read().splitlines() + +# read test image file names +with open('../Flickr8k_text/Flickr_8k.testImages.txt','r') as ts_imgs: + test_imgs = ts_imgs.read().splitlines() + +# read image captions +with open('../Flickr8k_text/Flickr8k.token.txt','r') as img_tkns: + captions = img_tkns.read().splitlines() +# combine dev and train image names into one set +train_imgs = train_imgs + dev_imgs +``` + +现在我们已经整理好输入图像的文件名并加载了相应的标题,我们需要构建一个基于字典的映射,该映射将源图像及其对应的标题映射在一起。 正如我们前面提到的,一个图像由五个不同的人字幕,因此,我们将为每个图像列出五个字幕。 下面的代码可以帮助我们做到这一点: + +```py +from collections import defaultdict + +caption_map = defaultdict(list) +# store five captions in a list for each image +for record in captions: + record = record.split('\t') + img_name = record[0][:-2] + img_caption = record[1].strip() + caption_map[img_name].append(img_caption) +``` + +我们稍后将在构建数据集进行培训和测试时利用此功能。 现在让我们集中讨论特征提取。 在提取图像特征之前,我们需要将原始输入图像预处理为正确的大小,并根据将要使用的模型缩放像素值。 以下代码将帮助我们进行必要的图像预处理步骤: + +```py +from keras.preprocessing import image +from keras.applications.vgg16 import preprocess_input as preprocess_vgg16_input + +def process_image2arr(path, img_dims=(224, 224)): + img = image.load_img(path, target_size=img_dims) + img_arr = image.img_to_array(img) + img_arr = np.expand_dims(img_arr, axis=0) + img_arr = preprocess_vgg16_input(img_arr) + return img_arr +``` + +我们还需要加载预训练的 VGG-16 模型以利用转移学习。 这是通过以下代码片段实现的: + +```py +from keras.applications import vgg16 +from keras.models import Model + +vgg_model = vgg16.VGG16(include_top=True, weights='imagenet', + input_shape=(224, 224, 3)) +vgg_model.layers.pop() +output = vgg_model.layers[-1].output +vgg_model = Model(vgg_model.input, output) +vgg_model.trainable = False + +vgg_model.summary() + +_________________________________________________________________ +Layer (type) Output Shape Param # +================================================================= +input_1 (InputLayer) (None, 224, 224, 3) 0 +_________________________________________________________________ +block1_conv1 (Conv2D) (None, 224, 224, 64) 1792 +_________________________________________________________________ +... +... +block5_conv3 (Conv2D) (None, 14, 14, 512) 2359808 +_________________________________________________________________ +block5_pool (MaxPooling2D) (None, 7, 7, 512) 0 +_________________________________________________________________ +flatten (Flatten) (None, 25088) 0 +_________________________________________________________________ +fc1 (Dense) (None, 4096) 102764544 +_________________________________________________________________ +fc2 (Dense) (None, 4096) 16781312 +================================================================= +Total params: 134,260,544 +Trainable params: 0 +Non-trainable params: 134,260,544 +_________________________________________________________________ +``` + +很明显,我们删除了 softmax 层,并使模型不可训练,因为我们只对从输入图像中提取密集的特征向量感兴趣。 现在,我们将构建一个利用我们的实用程序功能并帮助从输入图像中提取正确功能的功能: + +```py +def extract_tl_features_vgg(model, image_file_name, + image_dir='../Flickr8k_imgs/'): + pr_img = process_image2arr(image_dir+image_file_name) + tl_features = model.predict(pr_img) + tl_features = np.reshape(tl_features, tl_features.shape[1]) + return tl_features +``` + +现在,我们通过提取图像特征并构建训练和测试数据集来对所有先前的功能和预先训练的模型进行测试: + +```py +img_tl_featureset = dict() +train_img_names = [] +train_img_captions = [] +test_img_names = [] +test_img_captions = [] + +for img in train_imgs: + img_tl_featureset[img] = extract_tl_features_vgg(model=vgg_model, + image_file_name=img) + for caption in caption_map[img]: + train_img_names.append(img) + train_img_captions.append(caption) + +for img in test_imgs: + img_tl_featureset[img] = extract_tl_features_vgg(model=vgg_model, + image_file_name=img) + for caption in caption_map[img]: + test_img_names.append(img) + test_img_captions.append(caption) + +train_dataset = pd.DataFrame({'image': train_img_names, 'caption': + train_img_captions}) +test_dataset = pd.DataFrame({'image': test_img_names, 'caption': + test_img_captions}) +print('Train Dataset Size:', len(train_dataset), '\tTest Dataset Size:', len(test_dataset)) + +Train Dataset Size: 35000 Test Dataset Size: 5000 +``` + +我们还可以通过使用以下代码来查看火车数据集的外观: + +```py +train_dataset.head(10) +``` + +前面代码的输出如下: + +![](img/20a72719-13fb-4588-858b-e498eed2b063.png) + +显然,每个输入图像都有五个标题,并且将其保留在数据集中。 现在,我们将这些数据集的记录和从转移学习中学到的图像特征保存到磁盘上,以便我们可以在模型训练期间轻松地将其加载到内存中,而不必每次运行模型时都提取这些特征: + +```py +# save dataset records +train_dataset = train_dataset[['image', 'caption']] +test_dataset = test_dataset[['image', 'caption']] + +train_dataset.to_csv('image_train_dataset.tsv', sep='\t', index=False) +test_dataset.to_csv('image_test_dataset.tsv', sep='\t', index=False) + +# save transfer learning image features +from sklearn.externals import joblib +joblib.dump(img_tl_featureset, 'transfer_learn_img_features.pkl') + +['transfer_learn_img_features.pkl'] +``` + +另外,如果需要,您可以使用以下代码段进行一些初始检查来验证图像功能的外观: + +```py +[(key, value.shape) for key, value in + img_tl_featureset.items()][:5] + +[('3079787482_0757e9d167.jpg', (4096,)), + ('3284955091_59317073f0.jpg', (4096,)), + ('1795151944_d69b82f942.jpg', (4096,)), + ('3532192208_64b069d05d.jpg', (4096,)), + ('454709143_9c513f095c.jpg', (4096,))] + +[(k, np.round(v, 3)) for k, v in img_tl_featureset.items()][:5] + +[('3079787482_0757e9d167.jpg', + array([0., 0., 0., ..., 0., 0., 0.], dtype=float32)), + ('3284955091_59317073f0.jpg', + array([0.615, 0\. , 0.653, ..., 0\. , 1.559, 2.614], dtype=float32)), + ('1795151944_d69b82f942.jpg', + array([0\. , 0\. , 0\. , ..., 0\. , 0\. , 0.538], dtype=float32)), + ('3532192208_64b069d05d.jpg', + array([0\. , 0\. , 0\. , ..., 0\. , 0\. , 2.293], dtype=float32)), + ('454709143_9c513f095c.jpg', + array([0\. , 0\. , 0.131, ..., 0.833, 4.263, 0\. ], dtype=float32))] +``` + +我们将在建模的下一部分中使用这些功能。 + +# 为我们的字幕建立词汇 + +下一步涉及对字幕数据进行一些预处理,并为字幕构建词汇表或元数据字典。 我们首先读取训练数据集记录并编写一个函数来预处理文本标题: + +```py +train_df = pd.read_csv('image_train_dataset.tsv', delimiter='\t') +total_samples = train_df.shape[0] +total_samples + +35000 + +# function to pre-process text captions +def preprocess_captions(caption_list): + pc = [] + for caption in caption_list: + caption = caption.strip().lower() + caption = caption.replace('.', '').replace(',', + '').replace("'", "").replace('"', '') + caption = caption.replace('&','and').replace('(','').replace(')', + '').replace('-', ' ') + caption = ' '.join(caption.split()) + caption = ' '+caption+' ' + pc.append(caption) + return pc +``` + +现在,我们将对字幕进行预处理,并为词汇建立一些基本的元数据,包括用于将唯一的单词转换为数字表示的工具,反之亦然: + +```py +# pre-process caption data +train_captions = train_df.caption.tolist() +processed_train_captions = preprocess_captions(train_captions) + +tc_tokens = [caption.split() for caption in + processed_train_captions] +tc_tokens_length = [len(tokenized_caption) for tokenized_caption + in tc_tokens] + +# build vocabulary metadata +from collections import Counter + +tc_words = [word.strip() for word_list in tc_tokens for word in + word_list] +unique_words = list(set(tc_words)) +token_counter = Counter(unique_words) + +word_to_index = {item[0]: index+1 for index, item in + enumerate(dict(token_counter).items())} +word_to_index[''] = 0 +index_to_word = {index: word for word, index in + word_to_index.items()} +vocab_size = len(word_to_index) +max_caption_size = np.max(tc_tokens_length) +``` + +重要的是要确保将词汇表元数据保存到磁盘上,以便将来在任何时候都可以将其重新用于模型训练和预测。 否则,如果我们重新生成词汇表,则很有可能已使用其他版本的词汇表来训练模型,其中单词到数字的映射可能有所不同。 这将给我们带来错误的结果,并且我们将浪费宝贵的时间: + +```py +from sklearn.externals import joblib + +vocab_metadata = dict() +vocab_metadata['word2index'] = word_to_index +vocab_metadata['index2word'] = index_to_word +vocab_metadata['max_caption_size'] = max_caption_size +vocab_metadata['vocab_size'] = vocab_size +joblib.dump(vocab_metadata, 'vocabulary_metadata.pkl') + +['vocabulary_metadata.pkl'] +``` + +如果需要,您可以使用以下代码片段检查词汇元数据的内容,还可以查看常规预处理的文本标题对于其中一张图像的外观: + +```py +# check vocabulary metadata +{k: v if type(v) is not dict + else list(v.items())[:5] + for k, v in vocab_metadata.items()} + +{'index2word': [(0, ''), (1, 'nearby'), (2, 'flooded'), + (3, 'fundraising'), (4, 'snowboarder')], + 'max_caption_size': 39, + 'vocab_size': 7927, + 'word2index': [('reflections', 4122), ('flakes', 1829), + ('flexing', 7684), ('scaling', 1057), ('pretend', 6788)]} + +# check pre-processed caption +processed_train_captions[0] + +' a black dog is running after a white dog in the snow ' +``` + +在构建数据生成器功能时,我们将在不久的将来利用此功能,该功能将用作模型训练期间深度学习模型的输入。 + +# 构建图像标题数据集生成器 + +在消耗大量数据的任何复杂深度学习系统中,最重要的步骤之一就是构建高效的数据集生成器。 这在我们的系统中非常重要,尤其是因为我们将处理图像和文本数据。 除此之外,我们将处理序列模型,在训练过程中,我们必须多次将相同数据传递给我们的模型。 将列表中的所有数据解压缩后,预先构建数据集将是解决此问题的最无效的方法。 因此,我们将为我们的系统利用发电机的力量。 + +首先,我们将使用以下代码加载从迁移学习中学到的图像功能以及词汇元数据: + +```py +from sklearn.externals import joblib + +tl_img_feature_map = joblib.load('transfer_learn_img_features.pkl') +vocab_metadata = joblib.load('vocabulary_metadata.pkl') + +train_img_names = train_df.image.tolist() +train_img_features = [tl_img_feature_map[img_name] for img_name in train_img_names] +train_img_features = np.array(train_img_features) + +word_to_index = vocab_metadata['word2index'] +index_to_word = vocab_metadata['index2word'] +max_caption_size = vocab_metadata['max_caption_size'] +vocab_size = vocab_metadata['vocab_size'] + +train_img_features.shape + +(35000, 4096) +``` + +我们可以看到有 35,000 张图像,其中每张图像都有大小为 4,096 的密集特征向量表示。 现在的想法是构建一个模型数据集生成器,该生成器将生成(输入,输出)对。 对于我们的输入,我们将使用转换为密集特征向量的源图像以及相应的图像标题,在每个时间步添加一个单词。 对应的输出将是对应输入图像和标题的相同标题的下一个单词(必须预测)。 下图使此方法更加清晰: + +![](img/8439d2a4-1315-4146-9eb0-59f0841cbaeb.png) + +基于此体系结构,很明显,对于同一图像,在每个时间步上,我们都传递相同的特征向量,并保持每次添加一个单词的标题,同时传递下一个要预测的单词作为相应的输出进行训练 我们的模型。 以下函数将帮助我们实现这一点,我们利用 Python 生成器进行延迟加载并提高内存效率: + +```py +from keras.preprocessing import sequence + +def dataset_generator(processed_captions, transfer_learnt_features, vocab_size, max_caption_size, batch_size=32): + partial_caption_set = [] + next_word_seq_set = [] + img_feature_set = [] + batch_count = 0 + batch_num = 0 + + while True: + for index, caption in enumerate(processed_captions): + img_features = transfer_learnt_features[index] + for cap_idx in range(len(caption.split()) - 1): + partial_caption = [word_to_index[word] for word in + caption.split()[:cap_idx+1]] + partial_caption_set.append(partial_caption) + + next_word_seq = np.zeros(vocab_size) + next_word_seq[word_to_index + [caption.split()[cap_idx+1]]] = 1 + next_word_seq_set.append(next_word_seq) + img_feature_set.append(img_features) + batch_count+=1 + + if batch_count >= batch_size: + batch_num += 1 + img_feature_set = np.array(img_feature_set) + partial_caption_set = + sequence.pad_sequences( + sequences=partial_caption_set, + maxlen=max_caption_size, + padding='post') + next_word_seq_set = + np.array(next_word_seq_set) + + yield [[img_feature_set, partial_caption_set], + next_word_seq_set] + batch_count = 0 + partial_caption_set = [] + next_word_seq_set = [] + img_feature_set = [] +``` + +让我们尝试了解此功能的真正作用! 尽管我们确实在上图中描绘了一个不错的视觉效果,但现在我们将使用以下代码为`10`的批处理大小生成示例数据: + +```py +MAX_CAPTION_SIZE = max_caption_size +VOCABULARY_SIZE = vocab_size +BATCH_SIZE = 10 + +print('Vocab size:', VOCABULARY_SIZE) +print('Max caption size:', MAX_CAPTION_SIZE) +print('Test Batch size:', BATCH_SIZE) + +d = dataset_generator(processed_captions=processed_train_captions, + transfer_learnt_features=train_img_features, + vocab_size=VOCABULARY_SIZE, + max_caption_size=MAX_CAPTION_SIZE, + batch_size=BATCH_SIZE) +d = list(d) +img_features, partial_captions = d[0][0] +next_word = d[0][1] + +Vocab size: 7927 +Max caption size: 39 +Test Batch size: 10 +``` + +现在,我们可以使用以下代码从数据生成器函数验证返回的数据集的维数: + +```py +img_features.shape, partial_captions.shape, next_word.shape + +((10, 4096), (10, 39), (10, 7927)) +``` + +很明显,我们的图像特征本质上是每个向量中 4,096 个特征的密集向量。 在字幕的每个时间步都对同一图像重复相同的特征向量。 字幕生成的矢量的大小为`MAX_CAPTION_SIZE`,即`39`。 下一个单词通常以单次编码的方式返回,这对于用作 softmax 层的输入非常有用,以检查模型是否预测了正确的单词。 以下代码向我们展示了图像特征向量如何查找输入图像的`10`批量大小: + +```py +np.round(img_features, 3) + +array([[0\. , 0\. , 1.704, ..., 0\. , 0\. , 0\. ], + [0\. , 0\. , 1.704, ..., 0\. , 0\. , 0\. ], + [0\. , 0\. , 1.704, ..., 0\. , 0\. , 0\. ], + ..., + [0\. , 0\. , 1.704, ..., 0\. , 0\. , 0\. ], + [0\. , 0\. , 1.704, ..., 0\. , 0\. , 0\. ], + [0\. , 0\. , 1.704, ..., 0\. , 0\. , 0\. ]], dtype=float32) +``` + +如前所述,在批处理数据生成过程中的每个时间步都重复了相同的图像特征向量。 我们可以检查在输入给模型的每个时间步骤中标题的形成方式。 为了简单起见,我们仅显示前 11 个单词: + +```py +# display raw caption tokens at each time-step +print(np.array([partial_caption[:11] for partial_caption in + partial_captions])) + +[[6917 0 0 0 0 0 0 0 0 0 0] + [6917 2578 0 0 0 0 0 0 0 0 0] + [6917 2578 7371 0 0 0 0 0 0 0 0] + [6917 2578 7371 3519 0 0 0 0 0 0 0] + [6917 2578 7371 3519 3113 0 0 0 0 0 0] + [6917 2578 7371 3519 3113 6720 0 0 0 0 0] + [6917 2578 7371 3519 3113 6720 7 0 0 0 0] + [6917 2578 7371 3519 3113 6720 7 2578 0 0 0] + [6917 2578 7371 3519 3113 6720 7 2578 1076 0 0] + [6917 2578 7371 3519 3113 6720 7 2578 1076 3519 0]] + +# display actual caption tokens at each time-step +print(np.array([[index_to_word[word] for word in cap][:11] for cap + in partial_captions])) + +[['' '' '' '' '' '' '' '' '' '' ''] + ['' 'a' '' '' '' '' '' '' '' '' ''] + ['' 'a' 'black' '' '' '' '' '' '' '' ''] + ['' 'a' 'black' 'dog' '' '' '' '' '' '' ''] + ['' 'a' 'black' 'dog' 'is' '' '' '' '' + '' ''] + ['' 'a' 'black' 'dog' 'is' 'running' '' '' '' '' ''] + ['' 'a' 'black' 'dog' 'is' 'running' 'after' '' '' '' ''] + ['' 'a' 'black' 'dog' 'is' 'running' 'after' 'a' '' '' ''] + ['' 'a' 'black' 'dog' 'is' 'running' 'after' 'a' 'white' '' ''] + ['' 'a' 'black' 'dog' 'is' 'running' 'after' 'a' 'white' 'dog' '']] +``` + +我们可以清楚地看到在``符号后的每个步骤中如何将一个单词添加到输入标题,这表示文本标题的开始。 现在让我们看一下对应的下一单词生成输出(通常是根据两个输入预测的下一单词): + +```py +next_word + +array([[0., 0., 0., ..., 0., 0., 0.], + [0., 0., 0., ..., 0., 0., 0.], + [0., 0., 0., ..., 0., 0., 0.], + ..., + [0., 0., 0., ..., 0., 0., 0.], + [0., 0., 0., ..., 0., 0., 0.], + [0., 0., 0., ..., 0., 0., 0.]]) + +print('Next word positions:', np.nonzero(next_word)[1]) +print('Next words:', [index_to_word[word] for word in + np.nonzero(next_word)[1]]) + +Next word positions: [2578 7371 3519 3113 6720 7 2578 1076 3519 5070] +Next words: ['a', 'black', 'dog', 'is', 'running', 'after', 'a', 'white', 'dog', 'in'] +``` + +很清楚,下一个单词通常基于输入字幕中每个时间步的单词顺序指向字幕中的下一个正确单词。 这些数据将在训练期间的每个时期馈入我们的模型。 + +# 建立我们的图像语言编解码器深度学习模型 + +现在,我们拥有构建模型所需的所有基本组件和实用程序。 如前所述,我们将使用编码器-解码器深度学习模型架构来构建图像捕获系统。 + +以下代码帮助我们构建此模型的体系结构,在该模型中,我们将成对的图像特征和字幕序列作为输入,以预测每个时间步长的字幕中的下一个可能单词: + +```py +from keras.models import Sequential, Model +from keras.layers import LSTM, Embedding, TimeDistributed, Dense, RepeatVector, Activation, Flatten, concatenate + +DENSE_DIM = 256 +EMBEDDING_DIM = 256 +MAX_CAPTION_SIZE = max_caption_size +VOCABULARY_SIZE = vocab_size + +image_model = Sequential() +image_model.add(Dense(DENSE_DIM, input_dim=4096, activation='relu')) +image_model.add(RepeatVector(MAX_CAPTION_SIZE)) + +language_model = Sequential() +language_model.add(Embedding(VOCABULARY_SIZE, EMBEDDING_DIM, input_length=MAX_CAPTION_SIZE)) +language_model.add(LSTM(256, return_sequences=True)) +language_model.add(TimeDistributed(Dense(DENSE_DIM))) + +merged_output = concatenate([image_model.output, language_model.output]) +merged_output = LSTM(1024, return_sequences=False)(merged_output) +merged_output = (Dense(VOCABULARY_SIZE))(merged_output) +merged_output = Activation('softmax')(merged_output) + +model = Model([image_model.input, language_model.input], merged_output) +model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy']) + +model.summary() +``` + +前面的代码的输出如下: + +![](img/0864c303-7dad-4af7-a015-f9cb1c2dbaba.png) + +从前面的体系结构中我们可以看到,我们有一个图像模型,该模型更着重于处理基于图像的特征作为其输入,而语言模型则利用 LSTM 来处理每个图像标题中流入的单词序列。 最后一层是 softmax 层,具有 7,927 个单位,因为我们的词汇表中总共有 7,927 个唯一词,并且字幕中的下一个预测词将是其中一个作为输出生成的词。 我们还可以使用以下代码片段来可视化我们的模型架构: + +```py +from IPython.display import SVG +from keras.utils.vis_utils import model_to_dot + +SVG(model_to_dot(model, show_shapes=True, show_layer_names=False, + rankdir='TB').create(prog='dot', format='svg')) +``` + +前面的代码的输出如下: + +![](img/66a126af-3f84-48bf-bba6-b93cef70aa7e.png) + +# 训练我们的图像字幕深度学习模型 + +在开始训练模型之前,由于我们正在处理模型中的一些复杂组件,因此在模型的准确性在整个连续历元中都达到稳定状态的情况下,我们会在模型中使用回调来降低学习率。 这对于在不停止训练的情况下即时更改模型的学习率非常有帮助: + +```py +from keras.callbacks import ReduceLROnPlateau +reduce_lr = ReduceLROnPlateau(monitor='loss', factor=0.15, + patience=2, min_lr=0.000005) +``` + +让我们现在训练我们的模型! 我们已经将模型训练到大约 30 到 50 个纪元,并在大约 30 个纪元和 50 个纪元保存了模型: + +```py +BATCH_SIZE = 256 +EPOCHS = 30 +cap_lens = [(cl-1) for cl in tc_tokens_length] +total_size = sum(cap_lens) + +history = model.fit_generator( + dataset_generator(processed_captions=processed_train_captions, + transfer_learnt_features=train_img_features, + vocab_size=VOCABULARY_SIZE, + max_caption_size=MAX_CAPTION_SIZE, + batch_size=BATCH_SIZE), + steps_per_epoch=int(total_size/BATCH_SIZE), + callbacks=[reduce_lr], + epochs=EPOCHS, verbose=1) + +Epoch 1/30 +1617/1617 - 724s 448ms/step - loss: 4.1236 - acc: 0.2823 +Epoch 2/30 +1617/1617 - 725s 448ms/step - loss: 3.9182 - acc: 0.3150 +Epoch 3/30 +1617/1617 - 724s 448ms/step - loss: 3.8286 - acc: 0.3281 +... +... +Epoch 29/30 +1617/1617 - 724s 447ms/step - loss: 3.6443 - acc: 0.3885 +Epoch 30/30 +1617/1617 - 724s 448ms/step - loss: 3.4656 - acc: 0.4078 + +model.save('ic_model_rmsprop_b256ep30.h5') +``` + +保存该模型后,我们将继续训练并对其进行另外 20 个时期的训练,并在`50`处停止。 当然,您也可以随意在 Keras 中使用模型检查点定期自动保存它: + +```py +EPOCHS = 50 + +history_rest = model.fit_generator( + dataset_generator(processed_captions=processed_train_captions, + transfer_learnt_features=train_img_features, + vocab_size=VOCABULARY_SIZE, + max_caption_size=MAX_CAPTION_SIZE, + batch_size=BATCH_SIZE), + steps_per_epoch=int(total_size/BATCH_SIZE), + callbacks=[reduce_lr], + epochs=EPOCHS, verbose=1, initial_epoch=30) + +Epoch 31/50 +1617/1617 - 724s 447ms/step - loss: 3.3988 - acc: 0.4144 +Epoch 32/50 +1617/1617 - 724s 448ms/step - loss: 3.3633 - acc: 0.4184 +... +... +Epoch 49/50 +1617/1617 - 724s 448ms/step - loss: 3.1330 - acc: 0.4509 +Epoch 50/50 +1617/1617 - 724s 448ms/step - loss: 3.1260 - acc: 0.4523 + +model.save('ic_model_rmsprop_b256ep50.h5') +``` + +这样就结束了我们的模型训练过程; 我们已经成功地训练了图像字幕模型,并可以开始使用它来为新图像生成图像字幕。 + +模型训练技巧:图像字幕模型通常使用大量数据,并且在训练过程中涉及许多参数。 建议使用生成器来构建和生成数据以训练深度学习模型。 否则,您可能会遇到内存问题。 另外,在带有 Tesla K80 GPU 的 Amazon AWS p2.x 实例上,该模型在每个时期运行将近 12 分钟,因此请考虑在 GPU 上构建该模型,因为在传统系统上进行培训可能会花费很长时间。 + +我们还可以根据训练过程中的不同时期,查看有关模型准确性,损失和学习率的趋势: + +```py +epochs = list(range(1,51)) +losses = history.history['loss'] + history_rest.history['loss'] +accs = history.history['acc'] + history_rest.history['acc'] +lrs = history.history['lr'] + history_rest.history['lr'] + +f, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(14, 4)) +title = f.suptitle("Model Training History", fontsize=14) +f.subplots_adjust(top=0.85, wspace=0.35) + +ax1.plot(epochs, losses, label='Loss') +ax2.plot(epochs, accs, label='Accuracy') +ax3.plot(epochs, lrs, label='Learning Rate') + +ax1.set_xlabel('Epochs') +ax2.set_xlabel('Epochs') +ax3.set_xlabel('Epochs') +ax1.set_ylabel('Loss') +ax2.set_ylabel('Accuracy') +ax3.set_ylabel('Learning Rate') +``` + +前面的代码的输出如下: + +![](img/8761e861-000e-4a0a-a02a-c0c16476bb84.png) + +我们可以看到,在第 28 和 29 阶段,准确性略有下降,损失增加了,这导致我们的回调成功降低了学习率,从第 30 阶段开始提高了准确性。 这无疑为我们提供了有关模型行为的有用见解! + +# 评估我们的图像字幕深度学习模型 + +训练模型而不评估其性能根本没有任何意义。 因此,我们现在将在测试数据集上评估深度学习模型的性能,该数据集与`Flickr8K`数据集共有 1000 幅不同的图像。 我们从加载通常的依赖关系开始(如果您还没有的话): + +```py +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt + +pd.options.display.max_colwidth = 500 + +%matplotlib inline +``` + +# 加载数据和模型 + +下一步包括将必要的数据,模型和其他资产从磁盘加载到内存中。 我们首先加载测试数据集和训练有素的深度学习模型: + +```py +# load test dataset +test_df = pd.read_csv('image_test_dataset.tsv', delimiter='\t') + +# load the models +from keras.models import load_model + +model1 = load_model('ic_model_rmsprop_b256ep30.h5') +model2 = load_model('ic_model_rmsprop_b256ep50.h5') +``` + +现在,我们需要加载必要的元数据资产,例如之前为测试数据提取的图像功能以及词汇元数据: + +```py +from sklearn.externals import joblib + +tl_img_feature_map = joblib.load('transfer_learn_img_features.pkl') +vocab_metadata = joblib.load('vocabulary_metadata.pkl') +word_to_index = vocab_metadata['word2index'] +index_to_word = vocab_metadata['index2word'] +max_caption_size = vocab_metadata['max_caption_size'] +vocab_size = vocab_metadata['vocab_size'] +``` + +# 了解贪婪和波束搜索 + +为了从基于深度学习的神经图像字幕模型生成预测,请记住,它不像基本分类或分类模型那样简单。 我们将需要根据输入图像特征在每个时间步从模型中生成一系列单词。 有多种方式为字幕生成这些单词序列。 + +一种方法称为**采样**或 **g** **芦苇搜索**,我们从``令牌开始,输入图像特征,然后生成第一个单词 基于 LSTM 输出中的 *p1* 。 然后,我们将相应的预测词嵌入作为输入,并根据来自下一个 LSTM 的 *p2* 生成下一个词(以我们之前讨论的展开形式)。 继续此步骤,直到到达``令牌(表示字幕结束)为止,或者达到基于预定义阈值的令牌的最大可能长度。 + +第二种方法称为**波束搜索**,它比基于贪婪的搜索更有效,在基于贪婪的搜索中,我们在考虑到每个单词之前生成的单词的基础上,根据最高概率在每个步骤中选择最可能的单词 顺序,这正是采样的作用。 Beam 搜索扩展了贪婪搜索技术,并始终返回最可能输出的术语序列的列表。 因此,在构建每个序列时,为了在时间步 *t + 1* 生成下一项,而不是进行贪婪搜索并生成最可能的下一项,迭代地考虑了一组 *k* 最佳句子基于下一步扩展到所有可能的下一词。 *k* 的值通常是用户指定的参数,用于控制进行平行搜索或波束搜索以生成字幕序列的总数。 因此,在波束搜索中,我们以 *k* 最可能的单词作为字幕序列中的第一时间步输出开始,并继续生成下一个序列项,直到其中一个达到结束状态为止。 涵盖围绕波束搜索的详细概念的全部范围将不在当前范围之内,因此,如果您有兴趣,我们建议您查看有关 AI 上下文中波束搜索的任何标准文献。 + +# 实现基于波束搜索的字幕生成器 + +现在,我们将实现一种基于波束搜索的基本算法来生成字幕序列: + +```py +from keras.preprocessing import image, sequence + +def get_raw_caption_sequences(model, word_to_index, image_features, + max_caption_size, beam_size=1): + + start = [word_to_index['']] + caption_seqs = [[start, 0.0]] + + while len(caption_seqs[0][0]) < max_caption_size: + temp_caption_seqs = [] + for caption_seq in caption_seqs: + partial_caption_seq = sequence.pad_sequences( + [caption_seq[0]], + maxlen=max_caption_size, + padding='post') + next_words_pred = model.predict( + [np.asarray([image_features]), + np.asarray(partial_caption_seq)])[0] + next_words = np.argsort(next_words_pred)[-beam_size:] + + for word in next_words: + new_partial_caption, new_partial_caption_prob = + caption_seq[0][:], caption_seq[1] + new_partial_caption.append(word) + new_partial_caption_prob += next_words_pred[word] + temp_caption_seqs.append([new_partial_caption, + new_partial_caption_prob]) + + caption_seqs = temp_caption_seqs + caption_seqs.sort(key = lambda item: item[1]) + caption_seqs = caption_seqs[-beam_size:] + + return caption_seqs +``` + +这有助于我们使用波束搜索基于输入图像特征生成字幕。 但是,它是在每个步骤中基于先前标记的原始标记序列。 因此,我们将在此基础上构建一个包装器函数,该函数将利用先前的函数生成一个纯文本句子作为输入图像的标题: + +```py +def generate_image_caption(model, word_to_index_map, index_to_word_map, + image_features, max_caption_size, + beam_size=1): + + raw_caption_seqs = get_raw_caption_sequences(model=model, + word_to_index=word_to_index_map, + image_features=image_features, + max_caption_size=max_caption_size, + beam_size=beam_size) + raw_caption_seqs.sort(key = lambda l: -l[1]) + caption_list = [item[0] for item in raw_caption_seqs] + captions = [[index_to_word_map[idx] for idx in caption] + for caption in caption_list] + + final_captions = [] + for caption in captions: + start_index = caption.index('')+1 + max_len = len(caption) + if len(caption) < max_caption_size + else max_caption_size + end_index = caption.index('') + if '' in caption + else max_len-1 + proc_caption = ' '.join(caption[start_index:end_index]) + final_captions.append(proc_caption) + + return final_captions +``` + +我们还需要之前的字幕预处理功能,用于训练模型时用来预处理初始字幕: + +```py +def preprocess_captions(caption_list): + pc = [] + for caption in caption_list: + caption = caption.strip().lower() + caption = caption.replace('.', '') + .replace(',', '') + .replace("'", "") + .replace('"', '') + caption = caption.replace('&','and') + .replace('(','') + .replace(')', '') + .replace('-', ' ') + caption = ' '.join(caption.split()) + pc.append(caption) + return pc +``` + +# 了解和实施 BLEU 评分 + +现在,我们需要选择适当的模型性能评估指标,以评估模型的性能。 这里的一个相关指标是**双语评估学习**( **BLEU** )得分。 这是一种评估模型在翻译语言时的性能的出色算法。 BLEU 背后的动机是,所生成的输出越接近于人工翻译,则得分越高。 时至今日,它仍然是将模型输出与人为输出进行比较的最受欢迎的指标之一。 + +BLEU 算法的简单原理是针对一组参考字幕评估生成的文本字幕(通常针对一个或多个字幕评估一个字幕,在这种情况下,每个图像五个字幕)。 计算每个字幕的分数,然后在整个语料库中平均以得到质量的总体估计。 BLEU 分数始终介于 0 到 1 之间,分数接近 1 表示高质量的翻译。 甚至参考文本数据也不是完美的,因为人类在字幕图像期间也会出错,因此,其想法不是获得完美的 1,而是获得良好的整体 BLEU 分数。 + +我们将使用 NLTK 中的翻译模块中的`corpus_bleu(...)`函数( [http://www.nltk.org/api/nltk.translate.html#nltk.translate.bleu_score.corpus_bleu](http://www.nltk.org/api/nltk.translate.html#nltk.translate.bleu_score.corpus_bleu) )进行计算 BLEU 分数。 我们将计算 1、2、3 和 4 克的总累积 BLEU 分数。 如我们已实现的评估功能所示,为`bleu2`,`bleu3`和`bleu4`分数的每个 n-gram 分数分配了相等的权重: + +```py +from nltk.translate.bleu_score import corpus_bleu + +def compute_bleu_evaluation(reference_captions, + predicted_captions): + actual_caps = [[caption.split() for caption in sublist] + for sublist in reference_captions] + predicted_caps = [caption.split() + for caption in predicted_captions] + + bleu1 = corpus_bleu(actual_caps, + predicted_caps, weights=(1.0, 0, 0, 0)) + bleu2 = corpus_bleu(actual_caps, + predicted_caps, weights=(0.5, 0.5, 0, 0)) + bleu3 = corpus_bleu(actual_caps, + predicted_caps, + weights=(0.3, 0.3, 0.3, 0)) + bleu4 = corpus_bleu(actual_caps, predicted_caps, + weights=(0.25, 0.25, 0.25, 0.25)) + + print('BLEU-1: {}'.format(bleu1)) + print('BLEU-2: {}'.format(bleu2)) + print('BLEU-3: {}'.format(bleu3)) + print('BLEU-4: {}'.format(bleu4)) + + return [bleu1, bleu2, bleu3, bleu4] +``` + +# 评估测试数据的模型性能 + +现在已经准备好用于模型性能评估的所有组件。 为了评估模型在测试数据集上的性能,我们现在将使用传递学习来加载之前提取的图像特征,这些特征将作为模型的输入。 我们还将加载字幕,对其进行预处理,并将其作为每个图像的参考字幕列表进行分离,如下所示: + +```py +test_images = list(test_df['image'].unique()) +test_img_features = [tl_img_feature_map[img_name] + for img_name in test_images] +actual_captions = list(test_df['caption']) +actual_captions = preprocess_captions(actual_captions) +actual_captions = [actual_captions[x:x+5] + for x in range(0, len(actual_captions),5)] +actual_captions[:2] + +[['the dogs are in the snow in front of a fence', + 'the dogs play on the snow', + 'two brown dogs playfully fight in the snow', + 'two brown dogs wrestle in the snow', + 'two dogs playing in the snow'], + ['a brown and white dog swimming towards some in the pool', + 'a dog in a swimming pool swims toward sombody we cannot see', + 'a dog swims in a pool near a person', + 'small dog is paddling through the water in a pool', + 'the small brown and white dog is in the pool']] +``` + +您可以清楚地看到每个图像标题现在如何位于整齐的单独列表中,这些列表将在计算 BLEU 分数时形成我们的标题参考集。 现在,我们可以生成 BLEU 分数,并使用不同的光束大小值测试模型的性能。 这里描述了一些示例: + +```py +# Beam Size 1 - Model 1 with 30 epochs +predicted_captions_ep30bs1 = [generate_image_caption(model=model1, + word_to_index_map=word_to_index, + index_to_word_map=index_to_word, + image_features=img_feat, + max_caption_size=max_caption_size, + beam_size=1)[0] + for img_feat + in test_img_features] +ep30bs1_bleu = compute_bleu_evaluation( + reference_captions=actual_captions, + predicted_captions=predicted_captions_ep30bs1) + +BLEU-1: 0.5049574449416513 +BLEU-2: 0.3224643449851107 +BLEU-3: 0.22962263359362023 +BLEU-4: 0.1201459697546317 + +# Beam Size 1 - Model 2 with 50 epochs +predicted_captions_ep50bs1 = [generate_image_caption(model=model2, + word_to_index_map=word_to_index, + index_to_word_map=index_to_word, + image_features=img_feat, + max_caption_size=max_caption_size, + beam_size=1)[0] + for img_feat + in test_img_features] +ep50bs1_bleu = compute_bleu_evaluation( + reference_captions=actual_captions, + predicted_captions=predicted_captions_ep50bs1) + +``` + +您可以清楚地看到,随着我们开始考虑更高水平的 n-gram,分数开始下降。 总体而言,运行此过程非常耗时,要在波束搜索中获得更高的阶数会花费大量时间。 我们尝试了光束大小分别为 1、3、5 和 10 的实验。下表描述了每个实验的模型性能: + +![](img/0c77c2ea-ffb1-4db5-8e3b-35514628b0d0.png) + +我们还可以通过图表的形式轻松地将其可视化,以查看哪种模型参数组合为我们提供了具有最高 BLEU 得分的最佳模型: + +![](img/d091a430-a904-4c49-9b84-cc431209bfca.png) + +从上一张图可以很明显地看出,基于 BLEU 指标,我们在波束搜索期间具有 50 个历元且波束大小为 10 的模型为我们提供了最佳性能。 + +# 自动图片字幕功能! + +对我们的测试数据集进行评估是测试模型性能的好方法,但是我们如何开始在现实世界中使用模型并为全新照片加上标题呢? 在这里,我们需要一些知识来构建端到端系统,该系统以任何图像作为输入,并为我们提供自由文本的自然语言标题作为输出。 + +以下是我们的自动字幕生成器的主要组件和功能: + +* 字幕模型和元数据初始化器 +* 图像特征提取模型初始化器 +* 基于转移学习的特征提取器 +* 字幕产生器 + +为了使它通用,我们构建了一个类,该类利用了前面几节中提到的几个实用程序函数: + +```py +from keras.preprocessing import image +from keras.applications.vgg16 import preprocess_input as preprocess_vgg16_input +from keras.applications import vgg16 +from keras.models import Model + +class CaptionGenerator: + + def __init__(self, image_locations=[], + word_to_index_map=None, index_to_word_map=None, + max_caption_size=None, caption_model=None, + beam_size=1): + self.image_locs = image_locations + self.captions = [] + self.image_feats = [] + self.word2index = word_to_index_map + self.index2word = index_to_word_map + self.max_caption_size = max_caption_size + self.vision_model = None + self.caption_model = caption_model + self.beam_size = beam_size + + def process_image2arr(self, path, img_dims=(224, 224)): + img = image.load_img(path, target_size=img_dims) + img_arr = image.img_to_array(img) + img_arr = np.expand_dims(img_arr, axis=0) + img_arr = preprocess_vgg16_input(img_arr) + return img_arr + + def initialize_model(self): + + vgg_model = vgg16.VGG16(include_top=True, weights='imagenet', + input_shape=(224, 224, 3)) + vgg_model.layers.pop() + output = vgg_model.layers[-1].output + vgg_model = Model(vgg_model.input, output) + vgg_model.trainable = False + self.vision_model = vgg_model + + def process_images(self): + if self.image_locs: + image_feats = [self.vision_model.predict + (self.process_image2arr + (path=img_path)) for img_path + in self.image_locs] + image_feats = [np.reshape(img_feat, img_feat.shape[1]) for + img_feat in image_feats] + self.image_feats = image_feats + else: + print('No images specified') + + def generate_captions(self): + captions = [generate_image_caption(model=self.caption_model, + word_to_index_map=self.word2index, + index_to_word_map=self.index2word, + image_features=img_feat, + max_caption_size=self.max_caption_size, beam_size=self.beam_size)[0] + for img_feat in self.image_feats] + self.captions = captions +``` + +现在我们的字幕生成器已经实现,现在该将其付诸实践了! 为了测试字幕发生器,我们下载了几张全新的图像,这些图像在`Flickr8K`数据集中不存在。 我们从 Flickr 下载了特定的图像,这些图像遵循必要的基于商业使用的许可证,因此我们可以在本书中进行描述。 我们将在下一部分中展示一些演示。 + +# 为室外场景中的样本图像加字幕 + +我们从 Flickr 拍摄了几张针对各种户外场景的图像,并使用了我们的两个图像字幕模型为每个图像生成字幕,如下所示: + +```py +# load files +import glob +outdoor1_files = glob.glob('real_test/outdoor1/*') + +# initialize caption generators and generate captions +cg1 = CaptionGenerator(image_locations=outdoor1_files, word_to_index_map=word_to_index, index_to_word_map=index_to_word, + max_caption_size=max_caption_size, caption_model=model1, beam_size=3) +cg2 = CaptionGenerator(image_locations=outdoor1_files, word_to_index_map=word_to_index, index_to_word_map=index_to_word, + max_caption_size=max_caption_size, caption_model=model2, beam_size=3) +cg1.initialize_model() +cg1.process_images() +cg1.generate_captions() +cg2.initialize_model() +cg2.process_images() +cg2.generate_captions() + +model30ep_captions_outdoor1 = cg1.captions +model50ep_captions_outdoor1 = cg2.captions + +# plot images and their captions +fig=plt.figure(figsize=(13, 11)) +plt.suptitle('Automated Image Captioning: Outdoor Scenes 1', verticalalignment='top', size=15) +columns = 2 +rows = 3 +for i in range(1, columns*rows +1): + fig.add_subplot(rows, columns, i) + image_name = outdoor1_files[i-1] + img = image.load_img(image_name) + plt.imshow(img, aspect='auto') + modelep30_caption_text = 'Caption(ep30): '+ model30ep_captions_outdoor1[i-1] + modelep50_caption_text = 'Caption(ep50): '+ model50ep_captions_outdoor1[i-1] + plt.xlabel(modelep30_caption_text+'\n'+modelep50_caption_text,size=11, wrap=True) +fig.tight_layout() +plt.subplots_adjust(top=0.955) +``` + +前面代码的输出如下: + +![](img/c801b87d-0f66-4b0c-beb4-9f16a7998be1.png) + +根据前面的图像,您可以清楚地看到它已正确识别每个场景。 这不是一个完美的模型,因为我们可以清楚地看到它并没有在第二行的第二张图像中识别出狗,而是清楚地识别了一群人。 此外,我们的模型确实犯了一些颜色识别错误,例如将绿色球识别为红色球。 总体而言,生成的字幕绝对适用于源图像! + +以下图像是从更多样化的户外场景中摘录的,并基于流行的户外活动。 我们将专注于不同的活动,以查看我们的模型在不同类型的场景上的表现如何,而不是仅关注一个特定的场景: + +![](img/98d383df-6e05-485e-b91d-4915cf14b77e.png) + +在前面的图像中,我们专注于各种各样的户外活动,包括越野自行车,滑雪,冲浪,皮划艇和攀岩。 如果您查看生成的字幕,它们与每个场景都相关,并可以很好地描述它们。 在某些情况下,我们的模型会变得非常具体,甚至描述每个人的穿着。 但是,正如我们前面提到的,它会在几种情况下错误地识别颜色,可能可以通过添加更多数据以及对高分辨率图像进行训练来改善颜色。 + +# 为流行运动的样本图像加字幕 + +在模型测试的最后一部分,我们从 Flickr 拍摄了几张图像,这些图像专注于世界各地通常进行的各种体育运动。 我们肯定获得了一些有趣的结果,因为我们不仅仅关注一两个基于运动的场景。 生成此代码的代码与我们在上一节中使用的代码完全相同,只是源图像发生了变化。 与往常一样,笔记本中提供了详细的代码以供参考。 以下是我们的字幕生成器在第一批运动场景上的结果: + +![](img/0b8a131c-6589-4e31-bf10-c6de946f210a.png) + +在前面的图像中,我们可以清楚地看到训练有 50 个历元的模型在视觉上更详细地描述图像方面优于具有 30 个历元的模型。 这包括特定的球衣和服装颜色,例如白色,蓝色和红色。 我们还看到字幕中提到了一些具体的活动,例如:踢足球,看曲棍球的进球或在泥泞的赛道上驾驶。 这无疑为生成的字幕提供了更多的深度和含义。 我们的模型具有 30 个时期,因此在某些图像中所进行的确切运动方面也会犯一些错误。 + +现在,让我们看一下体育场景的最后一组,以了解我们的字幕生成器在与前一组场景完全不同的体育活动中的表现: + +![](img/d7ae1192-1196-4254-93cc-028dd18d4d65.png) + +我们可以从前面的输出中观察到,我们的两个模型都运行良好,在 30 个纪元上训练的模型在几种情况下都表现出色,例如识别出踢足球的孩子或男孩,甚至是比赛中 BMX 骑手的颜色和配饰。 总体而言,这两种模型都表现良好,并且在某种程度上解释了风景,类似于人类对这些场面的描述。 + +成功的主要方面是我们的模型不仅可以正确识别每个活动,而且还能够生成有意义且适用的标题。 我们鼓励您尝试在不同的场景上构建和测试自己的字幕生成器! + +# 未来的改进空间 + +根据我们在本章中采用的方法,有多种方法可以改进此模型。 以下是一些可以改进的特定方面: + +* 使用更好的图像特征提取模型,例如 Google 的 Inception 模型 +* 分辨率更高,质量更好的训练图像(需要 GPU 功能!) +* 基于 Flickr30K 等数据集甚至图像增强的更多训练数据 +* 在模型中引入注意 + +如果您拥有必要的数据和基础架构,那么这些点子值得探讨! + +# 摘要 + +这绝对是我们整本书中解决的最棘手的现实问题之一。 它是迁移学习和生成型深度学习的完美结合,可应用于来自图像和文本的数据组合,这些组合结合了围绕计算机视觉和 NLP 的不同领域。 我们介绍了有关理解图像字幕的基本概念,构建字幕生成器所需的主要组件,并从头开始构建了我们自己的模型。 我们通过利用预先训练的计算机视觉模型从要字幕的图像中提取正确的特征,然后将它们与一些顺序模型(例如 LSTM)结合使用,以有效地利用迁移学习原理。 顺序模型的有效评估非常困难,我们利用行业标准的 BLEU 评分标准来达到目的。 我们从头开始实施评分功能,并在测试数据集上评估了我们的模型。 + +最后,我们使用以前构建的所有资产和组件从头构建了一个通用的自动图像字幕系统,并在来自不同领域的多种图像上对其进行了测试。 我们希望这能给您一个很好的入门介绍,这是计算机视觉和 NLP 的完美结合,并且我们绝对鼓励您构建自己的图像捕获系统! \ No newline at end of file diff --git a/docs/handson-tl-py/11.md b/docs/handson-tl-py/11.md new file mode 100644 index 0000000000000000000000000000000000000000..5dc3f80d44adb29a425edd37985b65139bf990bf --- /dev/null +++ b/docs/handson-tl-py/11.md @@ -0,0 +1,394 @@ +# 图像着色 + +颜色是大自然的笑容。 + +–雷·亨特 + +直到 1840 年代,世界都是以黑白捕获。 加布里埃尔·利普曼(Gabriel Lippmann)于 1908 年获得诺贝尔物理学奖,从而开始了色彩捕捉的时代。 1935 年,伊士曼·柯达(Eastman Kodak)推出了一体式三重彩色胶卷,称为 *Kodachrome* ,用于拍摄彩色照片。 + +彩色图像不仅与美学和美感有关,而且比黑白图像捕获的信息要多得多。 颜色是现实世界对象的重要属性,它为我们对周围世界的感知增加了另一个维度。 色彩的重要性是如此之大,以至于有许多项目为整个历史上的艺术作品和摄影作品着色。 随着 Adobe Photoshop 和 GIMP 等工具的出现,人们一直在努力地将旧照片转换为彩色照片。 reddit r / Colorization 子组是一个在线社区,人们在这里分享经验并致力于将黑白图像转换为彩色图像。 + +到目前为止,在本书中,我们涵盖了不同的领域和场景,以展示迁移学习的惊人好处。 在本章中,我们将介绍使用深度学习进行图像着色的概念,并利用转移学习来改善结果。 本章将涵盖以下主题: + +* 问题陈述 +* 了解图像色彩 +* 彩色图像 +* 建立基于深度神经网络的着色网络 +* 改进之处 +* 挑战性 + +在接下来的部分中,我们将使用术语黑白,单色和灰度来表示没有任何颜色信息的图像。 我们将这些术语互换使用。 + +# 问题陈述 + +照片可以帮助我们及时保存事件。 它们不仅帮助我们重温记忆,还提供对过去重要事件的见解。 在彩色摄影成为主流之前,我们的摄影历史是用黑白拍摄的。 图像着色的任务是将给定的灰度图像转换为合理的颜色版本。 + +图像着色的任务可以从不同的角度进行。 手动过程非常耗时,并且需要出色的技能(请参阅 [https://www.reddit.com/r/Colorization/](https://www.reddit.com/r/Colorization/) 上的 **r / Colorization** subreddit)。 计算机视觉和深度学习领域的研究人员一直在研究使过程自动化的不同方法。 通过本章,我们将努力理解如何将深层神经网络用于此类任务。 我们还将尝试利用转移学习的力量来改善结果。 + +我们鼓励读者在继续进行之前,先对问题陈述进行思考。 考虑一下您将如何处理这样的任务。 在深入探讨该解决方案之前,让我们获取有关彩色图像和相关概念的一些信息。 下一节涵盖了处理当前任务所需的基本概念。 + +# 彩色图像 + +不到 100 年前,单色捕获是一个限制,而不是一种选择。 数码和移动摄影的出现使黑白图像或灰度图像成为一种艺术选择。 当然,这样的图像具有戏剧性的效果,但是黑白图像不仅仅是改变捕获设备(无论是数码相机还是电话)上的选项。 + +我们对颜色和正式颜色模型的了解早于彩色图像。 托马斯·杨(Thomas Young)在 1802 年提出了三种类型的感光器或视锥细胞的存在(如下图所示)。 他的理论详述了这三个视锥细胞中的每一个仅对特定范围的可见光敏感。 进一步发展了该理论,将这些视锥细胞分为短,中和长三种,分别优选蓝色,绿色和红色: + +![](img/8f1cb58e-6396-4356-882e-e773f540c85e.png) + +托马斯·杨(Thomas Young)和赫尔曼·赫尔姆霍尔茨(Hermann Helmholtz):三锥细胞理论(来源:https://en.wikipedia.org/wiki/Color_space#/media/File:YoungHelm.jpg) + +我们对颜色的理解以及对颜色的理解方式的进步导致了颜色理论的形式化。 由于色彩理论本身是一个完整的领域,因此在本章中,我们将对其进行简要介绍。 关于这些主题的详细讨论超出了本书的范围。 + +# 色彩理论 + +简单来说,色彩理论是用于指导色彩感知,混合,匹配和复制方法的正式框架。 多年来,已经进行了各种尝试来基于色轮,原色,第二色等正式定义颜色。 因此,颜色理论是一个广阔的领域,在此之下,我们可以正式定义与色度,色相,配方等颜色相关的属性。 + +# 色彩模型和色彩空间 + +颜色模型是颜色理论到颜色表示的表述。 颜色模型是一种抽象的数学概念,当与它的组成的精确理解相关联时,被称为**颜色空间**。 大多数颜色模型都用表示特定颜色成分的三到四个数字的元组表示。 + +# RGB + +托马斯·杨(Thomas Young)三锥理论**红色绿色蓝色**( **RGB** )的延续,是最古老,使用最广泛的颜色模型和颜色空间之一。 RGB 是加色模型。 在此模型中,以不同的浓度添加了光的三个分量(红色,绿色和蓝色),以实现可见光的完整光谱。 附加色空间如下图所示: + +![](img/4411249b-c6c4-4faf-bb66-9598ca848e6c.png) + +RGB 颜色空间(来源:英语 Wikipedia 的 SharkD。更高版本由 Jacobolus 上载,已从 en.wikipedia 转移到 Commons.Public 域,https://commons.wikimedia.org/w/index.php?curid = 2529435) + +每种成分的零强度会导致黑色,而全强度会导致对白色的感觉。 尽管简单,但此颜色模型和颜色空间构成了大多数电子显示器(包括 CRT,LCD 和 LED)的基础。 + +# YUV + +**Y** 代表**亮度**,而 **U** 和 **V** 通道代表**色度**。 该编码方案在视频系统中被广泛使用以映射人类的颜色感知。 紫外线通道主要帮助确定红色和蓝色的相对含量。 由于该方案使用较低的带宽并且不易出现传输错误的能力,因此被广泛使用,如下所示: + +![](img/d8116e36-e0dc-469c-8872-ca06a7d83261.png) + +YUV 色彩空间(来源:Tonyle,本人著作,CC BY-SA 3.0,https://commons.wikimedia.org/w/index.php?curid = 6977944) + +此图像是 UV 颜色通道在 0.5 Y 处的样本表示。 + +# 实验室 + +这种与设备无关的色彩空间参考是由国际照明委员会开发的。 **L** 通道表示颜色的亮度(0 为黑色,而 100 为漫射白色)。 + +**A** 表示绿色和品红色之间的位置,而 **B** 表示蓝色和黄色之间的位置,如下所示: + +![](img/9f139cfc-8df5-44ca-b88e-db6314c11a3b.png) + +LAB 色彩空间(来源:Holger kkk Everding –自己的作品,CC BY-SA 4.0,https://commons.wikimedia.org/w/index.php?curid = 38366968) + +除了这三种以外,还存在其他各种颜色模型。 出于当前有关图像着色的用例的目的,我们将采用一种非常有趣的方法。 + +# 重新陈述问题陈述 + +如果我们遵循使用最广泛的颜色模型 RGB,那么事实证明,训练模型以将输入的单色图像映射到颜色将是一项艰巨的任务。 + +深度学习领域的研究人员在解决和提出问题方面颇具创造力。 在图像着色的情况下,研究人员巧妙地研究了利用不同输入来实现灰度图像逼真的幻觉的方法。 + +在最初的尝试中,参考图像和颜色涂鸦形式的颜色引导输入的不同变体被用来产生巨大的效果。 请参阅威尔士和合著者以及莱文和合著者。 + +最近的工作集中在利用深层 CNN 中的转移学习使整个过程自动化。 结果令人鼓舞,有时甚至足以愚弄人类。 + +最近的工作,以及迁移学习的力量,已经巧妙地尝试利用包含灰度通道作为其组成部分之一的颜色模型。 那会响吗? 现在让我们从另一个角度看问题陈述。 + +除了无所不在的 RGB 颜色空间外,我们还讨论了 LAB。 LAB 色彩空间包含灰度值,因为 *L* 通道(用于亮度),而其余两个通道( *a* 和 *b* )赋予颜色属性。 因此,着色问题可以用以下数学方式建模: + +![](img/90336338-d933-4938-99f5-5a8295c39919.png) + +在上述方程式中,我们表示从给定数据将 *L* 通道映射到同一图像的 *a* 和 *b* 通道的函数。 下图说明了这一点: + +![](img/a5eb7160-f340-4ee3-9f73-81489bf2a779.png) + +colornet 转换 + +简而言之,我们已经将图像着色的任务转换为将一个通道(灰度 **L** 通道)转换为两个颜色通道( **A** 和 **B** )的任务 ,说明如下: + +![](img/9ff5a5d4-1ebb-4948-9c61-6e45b7a3a6d5.png) + +彩色图像及其组件-RGB,YUV 和 LAB + +前面的图像显示了彩色图像的 **L** , **A** 和 **B** 通道。 Zhang 及其合著者(2016)和 Federico 及其合著者(2017)( [https://arxiv.org/abs/1603.08511](https://arxiv.org/abs/1603.08511) [)](https://arxiv.org/abs/1603.08511)的作品 前提。 我们将在接下来的部分中详细研究它们。 + +我们鼓励读者阅读标题为*的深度可化:使用 CNN 和 Inception-ResNet-v2* ( [https://arxiv.org/abs/1712.03400](https://arxiv.org/abs/1712.03400) )进行图像着色的论文。 我们要感谢 Federico Baldassarre,Diego Gonzalez-Morin 和 Lucas Rodes-Guirao 为他们的工作及其实施提供了详细的信息和见解。 我们还要感谢 Emil Wallner 使用 Keras 出色地实现了本文。 + +读者应注意,类似的过程也可以应用于 YUV 色彩空间。 Jeff Hwang 和他的合著者 [http://cs231n.stanford.edu/reports/2016/在题为“利用深度卷积神经网络进行图像着色”的论文中讨论了利用这种色彩空间的尝试,效果也很好。 pdfs / 219_Report.pdf](http://cs231n.stanford.edu/reports/2016/pdfs/219_Report.pdf) )。 + +# 建立着色深层神经网络 + +现在是时候构建着色深层神经网络或色网。 如前一节所述,如果我们使用替代颜色空间,例如 LAB(或 YUV),则可以将着色任务转换为数学转换。 转换如下: + +![](img/3a9b6edb-d7da-4cc8-9007-9ebd67ed7f35.png) + +数学公式和创造力很好,但是学习这些转换的图像在哪里呢? 深度学习网络需要大量数据,但幸运的是,我们有来自各种开源数据集的大量不同图像的集合。 在本章中,我们将依赖于 ImageNet 本身的一些示例图像。 由于 ImageNet 是一个庞大的数据集,因此我们为问题陈述随机选择了一些彩色图像。 在后面的部分中,我们将讨论为什么选择此子集及其一些细微差别。 + +我们依靠 Baldassarre 及其合作者开发的图像提取实用程序进行 *Deep Koalarization:使用 CNN 和 Inception-ResNet-v2* 论文进行图像着色( [https://arxiv.org/ abs / 1712.03400](https://arxiv.org/abs/1712.03400) )来整理本章中使用的 ImageNet 样本的子集。 可以在 [https://github.com/baldassarreFe/deep-koalarization/tree/master/dataset](https://github.com/baldassarreFe/deep-koalarization/tree/master/dataset) 上获取数据提取的代码。 + +本书的 GitHub 存储库中提供了本章使用的代码和示例图像以及`colornet_vgg16.ipynb`笔记本。 + +# 前处理 + +获取/整理所需数据集后的第一步是预处理。 对于当前的图像着色任务,我们需要执行以下预处理步骤: + +* **重新缩放**:ImageNet 是一个具有各种图像的多样化数据集,包括类和大小(尺寸)。 为了实现此目的,我们将所有图像重新缩放为固定大小。 +* **利用 24 位 RGB** :由于人眼只能区分 2 和 1000 万种颜色,因此我们可以利用 24 位 RGB 来近似 1600 万种颜色。 减少每个通道的位数将有助于我们以更少的资源更快地训练模型。 这可以通过简单地将像素值除以 255 来实现。 +* **RGB 到 LAB** :由于在 LAB 色彩空间中更容易解决图像着色问题,因此我们将利用 skimage 来转换和提取 RGB 图像中的 LAB 通道。 + +# 标准化 + +LAB 颜色空间的值介于-128 至+128 之间。 由于神经网络对输入值的大小敏感,因此我们将从-128 到+128 的变换后的像素值归一化,并将它们置于-1 到+1 范围内。 以下代码片段中展示了相同的内容: + +```py +def prep_data(file_list=[], + dir_path=None, + dim_x=256, + dim_y=256): + + #Get images + + X = [] +for filename in file_list: + X.append(img_to_array( + sp.misc.imresize( + load_img( + dir_path+filename), + (dim_x, dim_y)) + + ) + + ) + X = np.array(X, dtype=np.float64) + X = 1.0/255*X + return X +``` + +转换后,我们将数据分为训练集和测试集。 对于拆分,我们使用了 sklearn 的`train_test_split utility`。 + +# 损失函数 + +模型是通过改善损失函数或目标函数来学习的。 任务是使用反向传播学习最佳参数,以最小化原始彩色图像和模型输出之间的差异。 来自模型的输出彩色图像也称为灰度图像的幻觉着色。 在此实现中,我们将**均方误差**( **MSE** )用作损失函数。 以下等式对此进行了总结: + +![](img/5b7fc827-b620-41f8-b1d3-6a35eacfc45e.png) + +原始颜色和色网输出之间的损失函数(来源:Baldassarre 和合著者) + +对于 Keras,使用此损失函数就像在编译 Keras 模型时设置参数一样容易。 我们利用 RMSprop 优化器来训练我们的模型(本文使用 Adam 代替)。 + +# 编码器 + +**卷积神经网络**( **CNN** )是令人惊叹的图像分类器。 他们通过提取位置不变特征来实现。 在此过程中,它们倾向于使输入图像失真。 + +在图像着色的情况下,这种失真将是灾难性的。 为此,我们使用编码器将 *H x W* 尺寸的输入灰度图像转换为 *H / 8 x W / 8* 。 编码器通过使用零填充来保持图像通过不同层的纵横比。 以下代码片段显示了使用 Keras 的编码器: + +```py +#Encoder +enc_input = Input(shape=(DIM, DIM, 1,)) +enc_output = Conv2D(64, (3,3), + activation='relu', + padding='same', strides=2)(enc_input) +enc_output = Conv2D(128, (3,3), + activation='relu', + padding='same')(enc_output) +enc_output = Conv2D(128, (3,3), + activation='relu', + padding='same', strides=2)(enc_output) +``` + +```py +enc_output = Conv2D(256, (3,3), + activation='relu', + padding='same')(enc_output) +enc_output = Conv2D(256, (3,3), + activation='relu', + padding='same', strides=2)(enc_output) +enc_output = Conv2D(512, (3,3), + activation='relu', + padding='same')(enc_output) +enc_output = Conv2D(512, (3,3), + activation='relu', + padding='same')(enc_output) +enc_output = Conv2D(256, (3,3), + activation='relu', + padding='same')(enc_output) +``` + +在前面的代码片段中,有趣的方面是对第 1 层,第 3 层和第 5 层使用了 2 的步幅大小。2 的步幅长度将图像尺寸减半,但仍设法保持了纵横比。 这有助于增加信息密度而不会扭曲原始图像。 + +# 转移学习–特征提取 + +本章讨论的图像着色网络是一个非常独特的网络。 它的独特性来自我们使用转移学习来增强模型的方式。 我们知道可以将预训练的网络用作特征提取器,以帮助转移学习的模式并提高模型的性能。 + +在这种当前设置下,我们利用预训练的 VGG16(本文指的是利用预训练的 Inception 模型)进行迁移学习。 由于 VGG16 需要以特定格式输入,因此我们通过调整输入图像的大小并将其连接 3 次以补偿丢失的通道信息,来转换输入的灰度图像(输入到网络编码器部分的相同灰度图像)。 + +以下代码段获取输入的灰度图像并生成所需的嵌入: + +```py +#Create embedding +def create_vgg_embedding(grayscaled_rgb): + gs_rgb_resized = [] + for i in grayscaled_rgb: + i = resize(i, (224, 224, 3), + mode='constant') + gs_rgb_resized.append(i) + gs_rgb_resized = np.array(gs_rgb_resized) + gs_rgb_resized = preprocess_input(gs_rgb_resized) + with vgg16.graph.as_default(): + embedding = vgg16.predict(gs_rgb_resized) + return embedding +``` + +前面的代码段生成大小为 1,000 x 1 x 1 的输出特征向量。 + +# 融合层 + +我们在前几章中构建的大多数网络都使用了 Keras 的顺序 API。 融合层是在这种情况下利用转移学习的创新方式。 请记住,我们已将输入灰度图像用作两个不同网络(一个编码器和一个预训练的 VGG16)的输入。 由于两个网络的输出具有不同的形状,因此我们将 VGG16 的输出重复 1,000 次,然后将其与编码器输出连接或合并。 以下代码段准备了融合层: + +```py +#Fusion +fusion_layer_output = RepeatVector(32*32)(emd_input) +fusion_layer_output = Reshape(([32,32, + 1000]))(fusion_layer_output) +fusion_layer_output = concatenate([enc_output, + fusion_layer_output], axis=3) +fusion_layer_output = Conv2D(DIM, (1, 1), + activation='relu', + padding='same')(fusion_layer_output) +``` + +VGG16 的输出重复沿编码器输出的深度轴连接。 这样可以确保从 VGG16 中提取的图像特征嵌入均匀地分布在整个图像中: + +![](img/ec57fb0d-1df6-4532-af60-06400ed77fd5.png) + +级联灰度输入预训练网络(左),融合层(右) +来源:Baldassarre 等 + +上图显示了特征提取器或预训练的 VGG16 的输入以及融合层的结构。 + +# 解码器 + +网络的最后阶段是解码器。 在网络的前两个部分中,我们利用编码器和预训练模型来学习不同的功能并生成嵌入。 融合层的输出为张量,大小为 H / 8 x W / 8 x 256,其中 *H* 和 *W* 是灰度图像的原始高度和宽度(在我们的 情况是 256 x 256)。 该输入经过一个八层解码器,该解码器使用五个卷积层和三个上采样层构建。 上采样层可帮助我们使用基本的最近邻方法将图像大小增加一倍。 以下代码片段展示了网络的解码器部分: + +```py +#Decoder +dec_output = Conv2D(128, (3,3), + activation='relu', + padding='same')(fusion_layer_output) +dec_output = UpSampling2D((2, 2))(dec_output) +dec_output = Conv2D(64, (3,3), + activation='relu', + padding='same')(dec_output) +dec_output = UpSampling2D((2, 2))(dec_output) +dec_output = Conv2D(32, (3,3), + activation='relu', + padding='same')(dec_output) +dec_output = Conv2D(16, (3,3), + activation='relu', + padding='same')(dec_output) +dec_output = Conv2D(2, (3, 3), + activation='tanh', + padding='same')(dec_output) +dec_output = UpSampling2D((2, 2))(dec_output) + +``` + +解码器网络的输出是具有两个通道的原始大小的图像,即,输出是形状为 *H xW x 2* 的张量。 最终的卷积层使用 tanh 激活函数将预测像素值保持在-1 到+1 范围内。 + +下图显示了具有三个组成部分的网络: + +![](img/29986810-292e-472d-97a8-f2a71c8ce21d.png) + +Colornet 由编码器,作为特征提取器的预训练模型,融合层和解码器组成 + +使用 Keras 构建的深度学习模型通常是使用顺序 API 构建的。 在这种情况下,我们的着色网络(即 colornet)利用功能性 API 来实现融合层。 + +# 后期处理 + +解决问题的技巧还没有结束。 如*预处理*小节中所述,我们将-1 到+1 之间的像素值标准化,以确保我们的网络正确训练。 同样,两个颜色通道的 LAB 颜色空间的值在-128 到+128 之间。 因此,执行以下两个后处理步骤: + +* 我们将每个像素值乘以 128,以将值带入所需的颜色通道范围 +* 我们将灰度输入图像与输出两通道图像连接起来,以获得幻觉的彩色图像 + +以下代码段执行后处理步骤,以产生幻觉的彩色图像: + +```py +sample_img = [] +for filename in test_files: + sample_img.append(sp.misc.imresize(load_img(IMG_DIR+filename), + (DIM, DIM))) +sample_img = np.array(sample_img, + dtype=float) +sample_img = 1.0/255*sample_img +sample_img = gray2rgb(rgb2gray(sample_img)) +sample_img = rgb2lab(sample_img)[:,:,:,0] +sample_img = sample_img.reshape(sample_img.shape+(1,)) +#embedding input +sample_img_embed = create_vgg_embedding(sample_img) +``` + +如前面的代码片段所示,我们使用 skimage 中的`lab2rgb`实用程序将生成的输出转换为 RGB 颜色空间。 这样做是为了便于可视化输出图像。 + +# 培训与结果 + +训练如此复杂的网络可能很棘手。 在本章中,我们从 ImageNet 中选择了一小部分图像。 为了帮助我们的网络学习和推广,我们使用 Keras 的`ImageDataGenerator`类来扩充数据集并在输入数据集中产生变化。 以下代码片段展示了图像增强和模型训练: + +```py +# Image transformer +datagen = ImageDataGenerator( + shear_range=0.2, + zoom_range=0.2, + rotation_range=20, + horizontal_flip=True) +def colornet_img_generator(X, + batch_size=BATCH_SIZE): + for batch in datagen.flow(X, batch_size=batch_size): + gs_rgb = gray2rgb(rgb2gray(batch)) + batch_lab = rgb2lab(batch) + + batch_l = batch_lab[:,:,:,0] + batch_l = batch_l.reshape(batch_l.shape+(1,)) + batch_ab = batch_lab[:,:,:,1:] / 128 + yield ([batch_l, + create_vgg_embedding(gs_rgb)], batch_ab) +history = model.fit_generator(colornet_img_generator(X_train, + BATCH_SIZE), + epochs=EPOCH, + steps_per_epoch=STEPS_PER_EPOCH) +``` + +在着色网络的情况下,这种损失可能会产生误导。 它似乎已稳定在 100 个纪元以下,但所产生的结果更多是乌贼色而不是颜色。 因此,我们做了更多的实验以达到以下结果: + +![](img/c43e6248-8076-41c3-b8cb-92a4470a4e47.png) + +Colornet 输出:第一列代表灰度输入,第二列代表模型输出,第三列代表原始图像 + +前面的结果虽然不令人吃惊,但令人鼓舞。 通过对模型进行 600 个时期的训练,批次大小为 64,可以实现上述结果。 + +# 挑战性 + +深度神经网络是功能强大的模型,具有成千上万个可学习的参数。 当前训练着色网络的方案提出了一系列新的挑战,其中一些挑战如下: + +* 当前的网络似乎已经学习了高级功能,例如草地和运动球衣(在一定程度上),而它发现学习较小物体的颜色模式有些困难。 +* 训练集仅限于非常具体的图像子集,因此反映在测试数据集中。 该模型对训练集中不存在的对象或包含这些对象的样本不多的性能不佳。 +* 即使训练损失似乎已稳定在 50 个纪元以下,但我们看到,除非进行数百个纪元训练,否则该模型的着色性能相当差。 +* 该模型很容易将大多数对象着色为灰色或棕褐色。 在训练了较少时期的模型中观察到了这一点。 + +除了这些挑战之外,对于如此复杂的体系结构,计算和内存要求也很高。 + +# 进一步改进 + +当前的实现尽管显示出令人鼓舞的结果,但是可以进一步调整。 通过利用更大,更多样化的数据集可以实现进一步的改进。 + +也可以通过使用功能更强大的最新预训练图像分类模型(例如 InceptionV3 或 InceptionResNetV2)来进行改进。 + +我们还可以通过准备由更复杂的架构组成的集成网络来利用 Keras 的功能性 API。 接下来的步骤之一可能是向网络提供时间信息,并查看是否还可以学习为视频着色。 + +# 摘要 + +图像着色是深度学习领域的前沿主题之一。 随着我们对迁移学习和深度学习的理解日趋成熟,应用范围变得越来越令人兴奋且更具创造力。 图像着色是研究的活跃领域,最近,深度学习专家分享了一些激动人心的工作。 + +在本章中,我们学习了颜色理论,不同的颜色模型和颜色空间。 这种理解帮助我们将问题陈述重新表述为从单通道灰度图像到两通道输出的映射。 然后,我们根据 Baldassarre 和他的合著者的作品,着手建立一个色网。 该实现涉及一个独特的三层网络,该网络由编码器,解码器和融合层组成。 融合层使我们能够通过将 VGG16 嵌入与编码器输出串联来利用传输学习。 网络需要一些特定的预处理和后处理步骤来训练给定的图像集。 我们的训练和测试数据集由 ImageNet 样本的子集组成。 我们对色网进行了数百次培训。 最后,我们提供了一些幻影图像,以了解该模型对着色任务的学习程度。 训练有素的色网学习了某些高级对象,例如草,但在较小或较不频繁的对象上表现不佳。 我们还讨论了这种类型的网络带来的一些挑战。 + +这结束了本书中由用例驱动的系列文章中的最后一章。 我们介绍了跨不同领域的不同用例。 每个用例都帮助我们利用了转移学习的概念,本书的前两部分对此进行了详细讨论。 机器学习和深度学习领域的领先人物之一安德鲁·伍(Andrew Ng)在他的 NIPS 2016 教程中表示: + +*转移学习将成为机器学习商业成功的下一个推动力。* + +在本书中讨论和展示了各种应用程序及其优势之后,您现在应该了解迁移学习的巨大潜力。 \ No newline at end of file diff --git a/docs/handson-tl-py/2.md b/docs/handson-tl-py/2.md new file mode 100644 index 0000000000000000000000000000000000000000..4b0b299244770d54ab5b24363be9fd9a029f3b5f --- /dev/null +++ b/docs/handson-tl-py/2.md @@ -0,0 +1,1124 @@ +# 深度学习基础 + +本章从深度学习真正含义的最基本基础开始,然后深入到围绕神经网络的其他基本概念和术语,深入探讨了深度学习的基本知识。 将向读者概述神经网络的基本构建模块,以及如何训练深度神经网络。 涵盖模型训练的概念,包括激活函数,损失函数,反向传播和超参数调整策略。 这些基础概念对于正在尝试深度神经网络模型的初学者和经验丰富的数据科学家都将有很大的帮助。 我们特别关注如何建立具有 GPU 支持的强大的基于云的深度学习环境,以及设置内部深度学习环境的技巧。 对于希望自己构建大规模深度学习模型的读者来说,这将非常有用。 本章将涵盖以下主题: + +* 什么是深度学习? +* 深度学习基础 +* 建立具有 GPU 支持的强大的基于云的深度学习环境 +* 建立具有 GPU 支持的强大的本地深度学习环境 +* 神经网络基础 + +# 什么是深度学习? + +在**机器学习**( **ML** )中,我们尝试自动发现用于将输入数据映射到所需输出的规则。 在此过程中,创建适当的数据表示形式非常重要。 例如,如果我们要创建一种将电子邮件分类为垃圾邮件/火腿的算法,则需要用数字表示电子邮件数据。 一个简单的表示形式可以是二进制矢量,其中每个组件从预定义的单词表中描述单词的存在与否。 同样,这些表示是与任务相关的,也就是说,表示可能会根据我们希望 ML 算法执行的最终任务而有所不同。 + +在前面的电子邮件示例中,如果我们要检测电子邮件中的情绪,则不必标识垃圾邮件/火腿,而更有用的数据表示形式可以是二进制矢量,其中预定义词汇表由具有正极性或负极性的单词组成。 大多数 ML 算法(例如随机森林和逻辑回归)的成功应用取决于数据表示的质量。 我们如何获得这些表示? 通常,这些表示是人为制作的功能,通过做出一些明智的猜测来进行迭代设计。 此步骤称为**特征工程**,是大多数 ML 算法中的关键步骤之一。 **支持向量机**( **SVM** )或一般的内核方法,试图通过将数据的手工表示转换为更高维度的空间来创建更相关的数据表示 使用分类或回归来解决 ML 任务的表示形式变得容易。 但是,SVM 很难扩展到非常大的数据集,并且在诸如图像分类和语音识别等问题上并不成功。 诸如随机森林和**梯度提升机**( **GBMs** )之类的集合模型创建了一组弱模型,这些模型专门用于很好地完成小任务,然后将这些弱模型组合到一些模型中 最终输出的方式。 当我们有非常大的输入尺寸时,它们工作得很好,而创建手工制作的功能是非常耗时的步骤。 总而言之,所有前面提到的 ML 方法都以浅浅的数据表示形式工作,其中涉及通过一组手工制作的特征进行数据表示,然后进行一些非线性转换。 + +深度学习是 ML 的一个子字段,在其中创建数据的分层表示。 层次结构的较高级别由较低级别的表示形式组成。 更重要的是,通过完全自动化 ML 中最关键的步骤(称为**特征工程**),可以从数据中自动学习这种表示层次。 在多个抽象级别上自动学习功能允许系统直接从数据中学习输入到输出的复杂表示形式,而无需完全依赖于人工制作的功能。 + +深度学习模型实际上是具有多个隐藏层的神经网络,它可以帮助创建输入数据的分层层次表示。 之所以称为*深*,是因为我们最终使用了多个隐藏层来获取表示。 用最简单的术语来说,深度学习也可以称为**分层特征工程**(当然,我们可以做更多的事情,但这是核心原理)。 深度神经网络的一个简单示例可以是具有多个隐藏层的**多层感知器**( **MLP** )。 下图中考虑基于 MLP 的人脸识别系统。 它学习到的最低级别的功能是对比度的一些边缘和图案。 然后,下一层能够使用那些局部对比的图案来模仿眼睛,鼻子和嘴唇。 最后,顶层使用这些面部特征创建面部模板。 深度网络正在组成简单的功能,以创建越来越复杂的功能,如下图所示: + +![](img/32d3a718-5dc8-4ced-a248-bf868068ea3c.png) + +具有深度神经网络的分层特征表示(来源:https://www.rsipvision.com/exploring-deep-learning/) + +为了理解深度学习,我们需要对神经网络的构建模块,如何训练这些网络以及如何将这样的训练算法扩展到非常大的深度网络有一个清晰的了解。 在深入探讨有关神经网络的更多细节之前,让我们尝试回答一个问题:为什么现在要进行深度学习? 神经网络的理论,甚至是**卷积神经网络**( **CNN** )都可以追溯到 1990 年代。 他们之所以变得越来越受欢迎的原因归结于以下三个原因: + +* **高效硬件的可用性**:摩尔定律使 CPU 具有更好,更快的处理能力和计算能力。 除此之外,GPU 在大规模计算数百万个矩阵运算中也非常有用,这是任何深度学习模型中最常见的运算。 诸如 CUDA 之类的 SDK 的可用性已帮助研究社区重写了一些可高度并行化的作业,以在少数 GPU 上运行,从而取代了庞大的 CPU 集群。 模型训练涉及许多小的线性代数运算,例如矩阵乘法和点积,这些运算在 CUDA 中非常有效地实现以在 GPU 中运行。 +* **大型数据源的可用性和更便宜的存储**:现在,我们可以免费访问大量带标签的文本,图像和语音训练集。 + +* **用于训练神经网络的优化算法的进展**:传统上,只有一种算法可用于学习神经网络中的权重,梯度下降或**随机梯度下降**( **SGD** )。 SGD 具有一些局限性,例如卡在局部最小值和收敛速度较慢,这些都可以通过较新的算法来克服。 我们将在后面的*神经网络基础知识*的后续部分中详细讨论这些算法。 + +# 深度学习框架 + +深度学习广泛普及和采用的主要原因之一是 Python 深度学习生态系统,它由易于使用的开源深度学习框架组成。 但是,考虑到新框架如何不断发布以及旧框架将要寿终正寝,深度学习的格局正在迅速变化。 深度学习爱好者可能知道 Theano 是由 Yoshua Bengio 领导的 MILA( [https://mila.quebec/](https://mila.quebec/) )创建的第一个也是最受欢迎的深度学习框架。 不幸的是,最近宣布,在 Theano 的最新版本(1.0)于 2017 年发布之后,对 Theano 的进一步开发和支持将结束。因此,了解那里可以利用哪些框架来实施和解决问题至关重要。 深度学习。 这里要记住的另一点是,几个组织本身正在建立,获取和启动这些框架(通常试图在更好的功能,更快的执行等方面相互竞争),以使每个人都受益。 下图展示了截至 2018 年最受欢迎的一些深度学习框架: + +![](img/7ff92d2e-11bf-4c54-b366-f0f8d512d881.png) + +您还可以在**迈向数据科学**( [https://towardsdatascience.com/battle-of-the-deep-learning-frameworks- 有关更多详细信息,请参见 part-i-cff0e3841750](https://towardsdatascience.com/battle-of-the-deep-learning-frameworks-part-i-cff0e3841750) )。 让我们简要看看一些最受欢迎的深度学习框架: + +* **Theano** :默认情况下,Theano 是一个低级框架,可对多维数组(现在通常称为**张量**)进行高效的数值计算。 Theano 非常稳定,语法与 TensorFlow 非常相似。 它确实具有 GPU 支持,但功能有限,特别是如果我们要使用多个 GPU。 由于其在 1.0 之后的开发和停止支持,如果您打算将 theano 用于深度学习实现,则应格外小心。 +* **TensorFlow** :这可能是最流行(或至少最流行)的深度学习框架。 它由 Google Brain 创建并于 2015 年开源,迅速吸引了 ML,深度学习研究人员,工程师和数据科学家的关注。 尽管初始发行版的性能存在问题,但它仍处于积极开发中,并且每个发行版都在不断完善。 TensorFlow 支持基于多 CPU 和 GPU 的执行,并支持多种语言,包括 C ++,Java,R 和 Python。 它仅用于支持符号编程样式来构建深度学习模型,该模型稍微复杂一些,但是自 v1.5 起广泛采用,它开始支持更流行且易于使用的命令式编程样式(也称为 **渴望执行**)。 TensorFlow 通常是类似于 Theano 的低级库,但也具有利用高级 API 进行快速原型设计和开发的功能。 TensorFlow 的重要部分还包括`tf.contrib`模块,该模块包含各种实验功能,包括 Keras API 本身! +* **Keras** :如果发现自己对利用底层深度学习框架来解决问题感到困惑,则可以始终依靠 Keras! 具有不同技能的人们广泛使用此框架,包括可能不是核心开发人员的科学家。 这是因为 Keras 提供了一个简单,干净且易于使用的高级 API,用于以最少的代码构建有效的深度学习模型。 这样做的好处是可以将其配置为在包括 theano 和 TensorFlow 在内的多个低级深度学习框架(称为**后端**)之上运行。 可通过 [https://keras.io/](https://keras.io/) 访问 Keras 文档,并且非常详细。 +* **Caffe** :这也是伯克利视觉与学习中心以 C ++(包括 Python 绑定)开发的第一个且相对较旧的深度学习框架之一。 关于 Caffe 的最好之处在于,它作为 Caffe Model Zoo 的一部分提供了许多预训练的深度学习模型。 Facebook 最近开放了 Caffe2 的源代码,它基于 Caffe 进行了改进和功能,并且比其前身更易于使用。 + +* **PyTorch:** Torch 框架是用 Lua 编写的,非常灵活和快速,通常可以带来巨大的性能提升。 PyTorch 是用于构建深度学习模型的基于 Python 的框架,它从 Torch 汲取了灵感。 它不仅是 Torch 的扩展或 Python 包装器,而且本身就是一个完整的框架,从而改进了 Torch 框架体系结构的各个方面。 这包括摆脱容器,利用模块以及性能改进(例如内存优化)。 +* **CNTK** :Cognitive Toolkit 框架已由 Microsoft 开源,并且支持 Python 和 C ++。 语法与 Keras 非常相似,并且支持多种模型架构。 尽管不是很流行,但这是 Microsoft 内部用于其几种认知智能功能的框架。 +* **MXNet** :这是由**分布式机器学习社区**( **DMLC** )开发的,该软件包是非常受欢迎的 XGBoost 软件包的创建者。 现在这是一个官方的 Apache Incubator 项目。 MXNet 是最早支持各种语言(包括 C ++,Python,R 和 Julia)以及多种操作系统(包括 Windows)的深度学习框架之一,而其他 Windows 常常会忽略该框架。 该框架非常高效且可扩展,并支持多 GPU。 因此,它已成为 Amazon 选择的深度学习框架,并为此开发了一个高级界面,称为 **Gluon** 。 +* **Gluon** :这是一个高级深度学习框架,或者说是接口,可以在 MXNet 和 CNTK 的基础上加以利用。 Gluon 由 Amazon AWS 和 Microsoft 联合开发,与 Keras 非常相似,可以被视为直接竞争对手。 然而,它声称它将随着时间的推移支持更多的低层深度学习框架,并具有使**人工智能**( **AI** )民主化的愿景。 Gluon 提供了一个非常简单,干净和简洁的 API,任何人都可以使用它以最少的代码轻松构建深度学习架构。 +* **BigDL** :将 BigDL 视为大规模的大数据深度学习! 该框架由 Intel 开发,可以在 Apache Spark 之上利用,以在 Hadoop 集群上以分布式方式构建和运行深度学习模型,作为 Spark 程序。 它还利用非常流行的英特尔**数学内核库**( **MKL** )来提高性能并提高性能。 + +上面的框架列表绝对不是深度学习框架的详尽列表,但是应该使您对深度学习领域中的内容有个很好的了解。 随意探索这些框架,并根据最适合您的情况选择任何一个。 + +永远记住,有些框架的学习曲线很陡峭,所以如果花时间学习和利用它们,不要灰心。 尽管每个框架都有各自的优点和缺点,但您应始终将更多的精力放在要解决的问题上,然后利用最适合解决问题的框架。 + +# 建立具有 GPU 支持的基于云的深度学习环境 + +深度学习在带有 CPU 的标准单 PC 设置中效果很好。 但是,一旦您的数据集开始增加大小,并且模型体系结构开始变得更加复杂,您就需要开始考虑在强大的深度学习环境中进行投资。 主要期望是该系统可以有效地构建和训练模型,花费较少的时间来训练模型,并且具有容错能力。 大多数深度学习计算本质上是数百万个矩阵运算(数据表示为矩阵),并且可以并行进行快速计算。 事实证明,GPU 在这方面可以很好地工作。 您可以考虑建立一个强大的基于云的深度学习环境,甚至是一个内部环境。 让我们看看如何在本节中建立一个强大的基于云的深度学习环境。 + +涉及的主要组件如下: + +* 选择云提供商 +* 设置您的虚拟服务器 +* 配置您的虚拟服务器 +* 安装和更新深度学习依赖项 +* 访问您的深度学习云环境 +* 在您的深度学习环境中验证 GPU 的启用 + +让我们更详细地研究这些组件中的每个组件,并逐步执行过程,以帮助您建立自己的深度学习环境。 + +# 选择云提供商 + +如今,有多家云提供商的价格可承受且具有竞争力。 我们希望利用**平台即服务**( **PaaS** )功能来管理数据,应用程序和基本配置。 + +下图显示了一些流行的云提供商: + +![](img/a1ec2d70-f56a-42f8-8ac8-08a88b943061.png) + +受欢迎的提供商包括亚马逊的 AWS,微软的 Azure 和 Google 的 **Google 云平台**( **GCP** )。 就本教程和本书而言,我们将利用 AWS。 + +# 设置您的虚拟服务器 + +您需要获取一个 AWS 账户才能执行本节中的其余步骤。 如果您还没有帐户,请转到 [https://aws.amazon.com/](https://aws.amazon.com/) 创建一个帐户。 准备就绪后,您可以登录 [https://console.aws.amazon.com/ec2/v2/](https://console.aws.amazon.com/ec2/v2/) 来登录您的帐户并导航到 AWS EC2 控制面板,该工具利用了 **弹性计算云**( **EC2** )服务,这是 Amazon 云计算服务的基础。 到达那里后,请记住选择一个您选择的区域(我通常与美国东部一起去),然后单击“启动实例”以启动在云上创建新虚拟服务器的过程: + +![](img/38d54157-0b1a-4a50-bced-6460281a2b6f.png) + +单击启动实例按钮应带您到该页面,以选择您自己的 **Amazon Machine Image** ( **AMI** )。 通常,AMI 由构建虚拟服务器所需的各种软件配置组成。 它包括以下内容: + +* 实例的根卷的模板,其中包括服务器的操作系统,应用程序和其他配置设置。 +* 启动许可设置,用于控制哪些 AWS 账户可以使用 AMI 启动实例。 +* 块设备映射,用于指定启动实例时要附加到实例的存储卷。 您所有的数据都在这里! + +我们将利用专门用于深度学习的预构建 AMI,因此我们不必花时间进行额外的配置和管理。 前往 AWS Marketplace 并选择深度学习 AMI(Ubuntu): + +![](img/35852910-b11e-45b5-a94f-f0cf8df4d9ac.png) + +选择 AMI 之后,您需要选择实例类型。 对于支持 GPU 的深度学习,我们建议使用 p2.xlarge 实例,该实例功能强大且经济实惠,每小时使用成本约为 0.90 美元(截至 2018 年)。 + +P2 实例最多可提供 16 个 NVIDIA K80 GPU,64 个 vCPU 和 732 GiB 主机内存,以及总共 192 GB 的 GPU 内存,如以下屏幕快照所示: + +![](img/55556fe2-bd04-47ea-a9c6-9660475b4950.png) + +接下来是配置实例详细信息。 除非希望启动多个实例,指定子网首选项以及指定关闭行为,否则可以保留默认设置。 + +下一步涉及添加存储详细信息。 通常,您具有根卷,可以在根卷中根据需要增加其大小,并添加额外的**弹性块存储**( **EBS** )卷以增加磁盘空间。 + +然后,我们看一下是否需要添加标签(区分大小写和键值对)。 目前我们不需要这个,所以我们跳过它。 + +我们将重点放在配置安全组的下一步上,特别是如果您想通过利用功能强大的 Jupyter Notebook 从外部访问深度学习设置。 为此,我们创建一个新的安全组并创建一个 Custom TCP 规则以打开并启用对端口`8888`的访问,如下所示: + +![](img/79fc8d9c-a2fb-49d6-8510-922dafbd2317.png) + +请注意,此规则通常允许任何 IP 监听您实例(我们将在其中运行 Jupyter Notebooks 的实例)上的端口(`8888`)。 如果需要,可以更改此设置,仅添加特定 PC 或笔记本电脑的 IP 地址,以提高安全性。 除此之外,我们稍后还将为 Jupyter Notebook 添加一个额外的密码保护功能,以提高安全性。 + +最后,您将需要通过创建密钥对(公钥和私钥)来启动实例,以安全地连接到实例。 如果您没有现有的密钥对,则可以创建一个新的密钥对,将私钥文件安全地存储到磁盘上,然后启动实例,如以下屏幕快照所示: + +![](img/496b8ebd-2671-47eb-9222-c5ba6849f120.png) + +请注意,虚拟服务器启动和启动可能需要几分钟,因此您可能需要稍等片刻。 通常,您可能会发现由于帐户的限制或容量不足而导致实例启动失败。 + +如果遇到此问题,您可以请求增加使用的特定实例类型的限制(在我们的例子中为`p2.xlarge`): + +![](img/75eb8b32-425b-4c71-aa65-fcf126587aca.png) + +通常,AWS 会在不到 24 小时内响应并批准您的请求,因此您可能需要稍等片刻才能获得批准,然后才可以启动实例。 启动实例后,您可以签出“实例”部分并尝试连接到该实例: + +![](img/2c2a21b9-8c3c-4008-802f-2ed5354d6f91.png) + +您可以使用本地系统中的命令提示符或终端(之前已存储了先前的私有 AWS 密钥)来立即连接到实例: + +```py +[DIP.DipsLaptop]> ssh -i "my-dl-box.pem" ubuntu@ec2-xxxxx.compute-1.amazonaws.com +Warning: Permanently added 'ec2-xxxxx.compute-1.amazonaws.com' (RSA) to the list of known hosts. + +======================================================================= +Deep Learning AMI for Ubuntu +======================================================================= +The README file for the AMI : /home/ubuntu/src/AMI.README.md +Welcome to Ubuntu 14.04.5 LTS (GNU/Linux 3.13.0-121-generic x86_64) + +Last login: Sun Nov 26 09:46:05 2017 from 10x.xx.xx.xxx +ubuntu@ip-xxx-xx-xx-xxx:~$ +``` + +因此,这使您能够成功登录到自己的基于云的深度学习服务器! + +# 配置您的虚拟服务器 + +让我们设置一些基本配置,以利用 Jupyter Notebook 的功能在虚拟服务器上进行分析和深度学习建模,而无需始终在终端上进行编码。 首先,我们需要设置 SSL 证书。 让我们创建一个新目录: + +```py +ubuntu@ip:~$ mkdir ssl +ubuntu@ip:~$ cd ssl +ubuntu@ip:~/ssl$ +``` + +进入目录后,我们将利用 OpenSSL 创建新的 SSL 证书: + +```py +ubuntu@ip:~/ssl$ sudo openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout "cert.key" -out "cert.pem" -batch +Generating a 1024 bit RSA private key +......++++++ +...++++++ +writing new private key to 'cert.key' +----- +ubuntu@ip:~/ssl2$ ls +cert.key cert.pem +``` + +现在,我们需要在前面提到的 Jupyter Notebook 中添加基于密码的安全性的附加层。 为此,我们需要修改 Jupyter 的默认配置设置。 如果您没有 Jupyter 的`config`文件,则可以使用以下命令生成它: + +```py +$ jupyter notebook --generate-config +``` + +要为笔记本计算机启用基于密码的安全性,我们需要首先生成一个密码及其哈希。 我们可以如下利用`Ipython.lib`中的`passwd()`函数: + +```py +ubuntu@ip:~$ ipython +Python 3.4.3 (default, Nov 17 2016, 01:08:31) +Type 'copyright', 'credits' or 'license' for more information +IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help. + +In [1]: from IPython.lib import passwd +In [2]: passwd() +Enter password: +Verify password: +Out[2]: 'sha1:e9ed12b73a30:142dff0cdcaf375e4380999a6ca17b47ce187eb6' +In [3]: exit +ubuntu@:~$ +``` + +输入密码并进行验证后,该函数将向您返回一个哈希值,即您的密码哈希值(在这种情况下,我键入的密码密钥实际上是单词`password`,因此您绝对不应使用 使用!)。 复制并保存该哈希值,因为我们很快将需要它。 + +接下来,启动您喜欢的文本编辑器以编辑 Jupyter `config`文件,如下所示: + +```py +ubuntu@ip:~$ vim ~/.jupyter/jupyter_notebook_config.py + +# Configuration file for jupyter-notebook. + +c = get_config() # this is the config object +c.NotebookApp.certfile = u'/home/ubuntu/ssl/cert.pem' +c.NotebookApp.keyfile = u'/home/ubuntu/ssl/cert.key' +c.IPKernelApp.pylab = 'inline' +c.NotebookApp.ip = '*' +c.NotebookApp.open_browser = False +c.NotebookApp.password = 'sha1:e9ed12b73a30:142dff0cdcaf375e4380999a6ca17b47ce187eb6' # replace this + +# press i to insert new text and then press 'esc' and :wq to save and exit + +ubuntu@ip:~$ +``` + +现在,在开始构建模型之前,我们将研究实现深度学习的一些基本依赖性。 + +# 安装和更新深度学习依赖项 + +深度学习有几个主要方面,并且针对 Python 利用 GPU 支持的深度学习。 我们将尽力介绍基本知识,但可以根据需要随时参考其他在线文档和资源。 您也可以跳过这些步骤,转到下一部分,以测试服务器上是否已启用启用 GPU 的深度学习。 较新的 AWS 深度学习 AMI 设置了支持 GPU 的深度学习。 + +但是,通常设置不是最好的,或者某些配置可能是错误的,因此(如果您看到深度学习没有利用您的 GPU,(从下一部分的测试中),您可能需要遍历这些知识。 脚步。 您可以转到*访问深度学习云环境*和*验证深度学习环境上的 GPU 启用*部分,以检查 Amazon 提供的默认设置是否有效。 然后,您无需麻烦执行其余步骤! + +首先,您需要检查是否已启用 Nvidia GPU,以及 GPU 的驱动程序是否已正确安装。 您可以利用以下命令进行检查。 请记住,p2.x 通常配备有 Tesla GPU: + +```py +ubuntu@ip:~$ sudo lshw -businfo | grep -i display +pci@0000:00:02.0 display GD 5446 +pci@0000:00:1e.0 display GK210GL [Tesla K80] + +ubuntu@ip-172-31-90-228:~$ nvidia-smi +``` + +如果正确安装了驱动程序,则应该看到类似于以下快照的输出: + +![](img/9da92d67-d3ef-48f5-9ea0-369abb747e48.png) + +如果出现错误,请按照以下步骤安装 Nvidia GPU 驱动程序。 切记根据您使用的 OS 使用其他驱动程序链接。 我有一个较旧的 Ubuntu 14.04 AMI,为此,我使用了以下命令: + +```py +# check your OS release using the following command +ubuntu@ip:~$ lsb_release -a +No LSB modules are available. +Distributor ID: Ubuntu +Description: Ubuntu 14.04.5 LTS +Release: 14.04 +Codename: trusty + +# download and install drivers based on your OS +ubuntu@ip:~$ http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1404/ x86_64/cuda-repo-ubuntu1404_8.0.61-1_amd64.deb +ubuntu@ip:~$ sudo dpkg -i ./cuda-repo-ubuntu1404_8.0.61-1_amd64.deb +ubuntu@ip:~$ sudo apt-get update +ubuntu@ip:~$ sudo apt-get install cuda -y +# Might need to restart your server once +# Then check if GPU drivers are working using the following command +ubuntu@ip:~$ nvidia-smi +``` + +如果您能够根据之前的命令查看驱动程序和 GPU 硬件详细信息,则说明驱动程序已成功安装! 现在,您可以集中精力安装 Nvidia CUDA 工具包。 通常,CUDA 工具包为我们提供了一个用于创建高性能 GPU 加速应用程序的开发环境。 这就是用来优化和利用我们 GPU 硬件的全部功能的工具。 您可以在 [https://developer.nvidia.com/cuda-toolkit](https://developer.nvidia.com/cuda-toolkit) 上找到有关 CUDA 的更多信息并下载工具包。 + +请记住,CUDA 非常特定于版本,并且我们的 Python 深度学习框架的不同版本仅与特定 CUDA 版本兼容。 我将在本章中使用 CUDA 8。 如果已经为您安装了 CUDA,并且与服务器上的深度学习生态系统一起正常工作,请跳过此步骤。 + +要安装 CUDA,请运行以下命令: + +```py +ubuntu@ip:~$ wget https://s3.amazonaws.com/personal-waf/cuda_8.0.61_375.26_linux.run + +ubuntu@ip:~$ sudo rm -rf /usr/local/cuda* + +ubuntu@ip:~$ sudo sh cuda_8.0.61_375.26_linux.run +# press and hold s to skip agreement and also make sure to select N when asked if you want to install Nvidia drivers + +# Do you accept the previously read EULA? +# accept + +# Install NVIDIA Accelerated Graphics Driver for Linux-x86_64 361.62? +# ************************* VERY KEY **************************** +# ******************** DON"T SAY Y ****************************** +# n + +# Install the CUDA 8.0 Toolkit? +# y + +# Enter Toolkit Location +# press enter + +# Do you want to install a symbolic link at /usr/local/cuda? +# y + +# Install the CUDA 8.0 Samples? +# y + +# Enter CUDA Samples Location +# press enter + +# Installing the CUDA Toolkit in /usr/local/cuda-8.0 … +# Installing the CUDA Samples in /home/liping … +# Copying samples to /home/liping/NVIDIA_CUDA-8.0_Samples now… +# Finished copying samples. +``` + +一旦安装了 CUDA,我们还需要安装 cuDNN。 该框架也由 Nvidia 开发,代表 **CUDA 深度神经网络**( **cuDNN** )库。 本质上,该库是 GPU 加速的库,由用于深度学习和构建深度神经网络的多个优化原语组成。 cuDNN 框架为标准深度学习操作和层(包括常规激活层,卷积和池化层,归一化和反向传播)提供了高度优化和优化的实现! 该框架的目的是加快深度学习模型的培训和性能,特别是针对 Nvidia GPU 的深度学习模型。 您可以在 [https://developer.nvidia.com/cudnn](https://developer.nvidia.com/cudnn) 上找到有关 cuDNN 的更多信息。 让我们使用以下命令安装 cuDNN: + +```py +ubuntu@ip:~$ wget https://s3.amazonaws.com/personal-waf/cudnn-8.0- + linux-x64-v5.1.tgz + +ubuntu@ip:~$ sudo tar -xzvf cudnn-8.0-linux-x64-v5.1.tgz +ubuntu@ip:~$ sudo cp cuda/include/cudnn.h /usr/local/cuda/include +ubuntu@ip:~$ sudo cp cuda/lib64/libcudnn* /usr/local/cuda/lib64 +ubuntu@ip:~$ sudo chmod a+r /usr/local/cuda/include/cudnn.h + /usr/local/cuda/lib64/libcudnn* +``` + +完成后,请记住使用您喜欢的编辑器(我们使用`vim`)将以下几行添加到`~/.bashrc`的末尾: + +```py +ubuntu@ip:~$ vim ~/.bashrc + +# add these lines right at the end and press esc and :wq to save and +# quit + +export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/cuda/lib64:/usr/local/cuda + /extras/CUPTI/lib64" +export CUDA_HOME=/usr/local/cuda +export DYLD_LIBRARY_PATH="$DYLD_LIBRARY_PATH:$CUDA_HOME/lib" +export PATH="$CUDA_HOME/bin:$PATH" + +ubuntu@ip:~$ source ~/.bashrc +``` + +通常,这会处理我们 GPU 的大多数必需依赖项。 现在,我们需要安装并设置 Python 深度学习依赖项。 通常,AWS AMI 随 Anaconda 发行版一起安装。 但是,如果它不存在,您可以始终参考 [https://www.anaconda.com/download](https://www.anaconda.com/download) 以根据 Python 和 OS 版本下载您选择的发行版。 通常,我们使用 Linux / Windows 和 Python 3 并利用本书中的 TensorFlow 和 Keras 深度学习框架。 在 AWS AMI 中,可能会安装不兼容的框架版本,这些框架版本不适用于 CUDA,或者可能是纯 CPU 版本。 以下命令安装 TensorFlow 的 GPU 版本,该版本在 CUDA 8 上最有效: + +```py +# uninstall previously installed versions if any +ubuntu@ip:~$ sudo pip3 uninstall tensorflow +ubuntu@ip:~$ sudo pip3 uninstall tensorflow-gpu + +# install tensorflow GPU version +ubuntu@ip:~$ sudo pip3 install --ignore-installed --upgrade https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.2.0-cp34-cp34m-linux_x86_64.whl +``` + +接下来,我们需要将 Keras 升级到最新版本,并删除所有剩余的`config`文件: + +```py +ubuntu@ip:~$ sudo pip install keras --upgrade +ubuntu@ip:~$ sudo pip3 install keras --upgrade +ubuntu@ip:~$ rm ~/.keras/keras.json +``` + +现在,我们几乎准备开始利用云上的深度学习设置。 紧紧抓住! + +# 访问您的深度学习云环境 + +我们真的不想一直坐在服务器上的终端并在其中进行编码。 由于我们要利用 Jupyter Notebook 进行交互式开发,因此我们将从本地系统访问云服务器上的 Notebook。 为此,我们首先需要在远程实例上启动 Jupyter Notebook 服务器。 + +登录到您的虚拟服务器并启动 Jupyter Notebook 服务器: + +```py +[DIP.DipsLaptop]> ssh -i my-dl-box.pem ubuntu@ec2-xxxxx.compute-1.amazonaws.com +=================================== +Deep Learning AMI for Ubuntu +=================================== +Welcome to Ubuntu 14.04.5 LTS (GNU/Linux 3.13.0-121-generic x86_64) +Last login: Sun Feb 25 18:23:47 2018 from 10x.xx.xx.xxx + +# navigate to a directory where you want to store your jupyter notebooks +ubuntu@ip:~$ cd notebooks/ +ubuntu@ip:~/notebooks$ jupyter notebook +[I 19:50:13.372 NotebookApp] Writing notebook server cookie secret to /run/user/1000/jupyter/notebook_cookie_secret +[I 19:50:13.757 NotebookApp] Serving notebooks from local directory: /home/ubuntu/notebooks +[I 19:50:13.757 NotebookApp] 0 active kernels +[I 19:50:13.757 NotebookApp] The Jupyter Notebook is running at: https://[all ip addresses on your system]:8888/ +[I 19:50:13.757 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation). +``` + +现在,我们需要在本地实例上启用端口转发,以便从本地计算机的浏览器访问服务器笔记本。 利用以下语法: + +```py +sudo ssh -i my-dl-box.pem -N -f -L local_machine:local_port:remote_machine:remote_port ubuntu@ec2-xxxxx.compute-1.amazonaws.com +``` + +这将开始将本地计算机的端口(在我的情况下为`8890`)转发到远程虚拟服务器的端口`8888`。 以下是我用于设置的内容: + +```py +[DIP.DipsLaptop]> ssh -i "my-dl-box.pem" -N -f -L localhost:8890:localhost:8888 ubuntu@ec2-52-90-91-166.compute-1.amazonaws.com +``` + +这也称为 **SSH 隧道**。 因此,一旦开始转发,请转到本地浏览器并导航到`localhost`地址`https://localhost:8890`,我们将其转发到虚拟服务器中的远程 Notebook 服务器。 确保您在地址中使用`https`,否则会收到 SSL 错误。 + +如果到目前为止您已正确完成所有操作,则应在浏览器中看到一个警告屏幕,并且按照以下屏幕截图中的步骤进行操作,则在任何笔记本上工作时,都应该会看到熟悉的 Jupyter 用户界面: + +![](img/484ebcb5-b14e-4c76-945e-b6562391ba5f.png) + +您可以放心地忽略“您的连接不是私人警告”; 之所以显示它,是因为我们自己生成了 SSL 证书,并且尚未得到任何可信机构的验证。 + +# 在您的深度学习环境中验证 GPU 的启用 + +最后一步是确保一切正常,并且我们的深度学习框架正在利用我们的 GPU(我们需要按小时支付!)。 您可以参考`Test GPU enabling.ipynb` Jupyter Notebook 来测试所有代码。 我们将在这里详细介绍。 我们首先要验证的是`keras`和`tensorflow`是否已正确加载到我们的服务器中。 可以通过如下导入它们来验证: + +```py +import keras +import tensorflow + +Using TensorFlow backend. +``` + +如果您看到前面的代码加载没有错误,那就太好了! 否则,您可能需要追溯以前执行的步骤,并在线搜索要获取的特定错误; 查看每个框架的 GitHub 存储库。 + +最后一步是检查`tensorflow`是否已启用以使用我们服务器的 Nvidia GPU。 您可以使用以下测试对此进行验证: + +```py +In [1]: from tensorflow.python.client import device_lib + ...: device_lib.list_local_devices() + +Out [1]: +[name: "/cpu:0" + device_type: "CPU" + memory_limit: 268435456 + locality { + } + incarnation: 9997170954542835749, + + name: "/gpu:0" + device_type: "GPU" + memory_limit: 11324823962 + locality { + bus_id: 1 + } + incarnation: 10223482989865452371 + physical_device_desc: "device: 0, name: Tesla K80, pci bus id: 0000:00:1e.0"] +``` + +如果您观察到上述输出,则可以看到我们的 GPU 列在设备列表中,因此在训练我们的深度学习模型时它将利用相同的 GPU。 您已经在云上成功建立了强大的深度学习环境,您现在可以使用它使用 GPU 来更快地训练深度学习模型! + +永远记住,AWS 按小时收费实例,并且您不希望在完成分析和构建模型后保持实例运行。 您始终可以根据需要从 EC2 控制台重新启动实例。 + +# 建立具有 GPU 支持的强大的本地深度学习环境 + +通常,用户或组织可能不希望利用云服务,尤其是在其数据敏感的情况下,因此要专注于构建本地深度学习环境。 这里的主要重点应该是投资于正确的硬件类型,以实现最佳性能并利用正确的 GPU 来构建深度学习模型。 关于硬件,特别强调以下方面: + +* **处理器**:如果您想宠爱自己,则可以投资 i5 或 i7 Intel CPU,或者 Intel Xeon。 +* **RAM** :为您的内存至少投资 32 GB DDR4 或更好的 RAM。 +* **磁盘**:1 TB 硬盘非常好,您还可以投资最少 128 GB 或 256 GB 的 SSD 来快速访问数据! +* **GPU** :也许是深度学习中最重要的组件。 投资 NVIDIA GPU,以及拥有 8 GB 以上 GTX 1070 的所有产品。 + +您不应该忽略的其他事项包括主板,电源,坚固的外壳和散热器。 + +设置完钻机之后,对于软件配置,您可以重复上一部分中的所有步骤,但不包括云设置,您应该一切顺利! + +# 神经网络基础 + +让我们尝试熟悉一下神经网络背后的一些基本概念,这些基本概念使所有深度学习模型都获得成功! + +# 一个简单的线性神经元 + +线性神经元是深度神经网络的最基本组成部分。 可以如下图所示。 在这里,![](img/0ee04466-01a3-4669-85c2-723a98d3aec6.png)代表输入向量, *w i s* 是神经元的权重。 给定一个包含一组输入目标值对的训练集,线性神经元尝试学习一种线性变换,该变换可以将输入向量映射到相应的目标值。 基本上,线性神经元通过线性函数![](img/6e193609-867e-44a1-a8ca-bcd3b07b2940.png)近似输入输出关系: + +![](img/9c4a31dd-9384-4d77-9229-5a45c60b24f9.png) + +简单线性神经元和简单非线性神经元的示意图 + +让我们尝试用这个简单的神经元为玩具问题建模。 员工 *A* 从自助餐厅购买午餐。 他们的饮食包括鱼,薯条和番茄酱。 他们每个人得到几个部分。 收银员只告诉他们一顿饭的总价。 几天后,他们能算出每份的价格吗? + +好吧,这听起来像一个简单的线性编程问题,可以很容易地通过解析来解决。 让我们使用前面的线性神经单元来表示这个问题。 在这里,![](img/6f233bb4-f697-48f7-9028-da2846d34002.png)和我们有相应的权重![](img/8b4c716c-2125-4744-b38f-367d4a598f62.png)。 + +每个进餐价格对各部分的价格给出线性约束: + +![](img/04c9762f-de07-4d2a-885b-beee4afb50ae.png) + +假设 *t n* 为真实价格, *y n* 由我们的模型估算的价格,由前面的线性方程式给出。 目标与我们的估计之间的剩余价格差为 *t n -y n* 。 现在,不同餐点的这些残差可以为正或负,并且可以抵消,从而使总体误差为零。 处理此问题的一种方法是使用平方和残差![](img/a7a8c6fa-4111-49ce-bcd9-4c63458b427c.png)。 如果我们能够最大程度地减少此错误,则可以对每件商品的一组权重/价格进行很好的估算。 因此,我们得出了一个优化问题。 让我们首先讨论一些解决优化问题的方法。 + +# 基于梯度的优化 + +优化基本上涉及最小化或最大化某些函数 *f(x)*,其中 *x* 是数值向量或标量。 在此, *f(x)*被称为**目标函数**或**准则**。 在神经网络中,我们称其为成本函数,损失函数或误差函数。 在前面的示例中,我们要最小化的损失函数为 *E* 。 + +假设我们有一个函数 *y = f(x)*,其中 *x* 和 *y* 是实数。 此函数的导数告诉我们此函数如何随 *x* 的微小变化而变化。 因此,可以通过无穷大地更改 *x* 来使用导数来减小函数的值。 假设 *f'(x)> 0* 为 *x* 。 这意味着,如果我们沿着 *x* 的正数增加 *x* ,则 *f(x)*将会增加,因此 *f(x-ε)< f(x )*以获得足够小的ε。 注意 *f(x)*可以通过在与导数方向相反的*中以小步长移动 *x* 来减少:* + +![](img/1f3da55c-feb9-4e0a-80b3-65112938b1ab.png) + +函数值沿导数的相反方向或相反方向的变化方式 + +如果导数 *f'(x)= 0* ,则导数不提供信息,我们需要朝哪个方向移动以达到函数最小值。 在局部最优(最小/最大)时,导数可以为零。 如果 *x ** 处的函数 *f(x)*的值等于 *x ** ,则将点称为**局部最小值**。 小于 *x ** 的所有相邻点。 同样,我们可以定义一个局部最大值。 某些点既不能是最大值,也不能是最小值,但是导数 *f'(x)*在这些点上为零。 这些称为**鞍座**点。 下图说明了 *f'(x)= 0* 的三种情况: + +![](img/c7eb65b7-41d2-4873-943e-07764f579812.png) + +单个变量函数的最小,最大和鞍点。 在所有三个突出显示的点上导数 f'(x)= 0 + +在 *x* 的所有可能值中达到 *f* 的最小值的点称为**全局最小值**。 一个函数可以具有一个或多个全局最小值。 可能存在局部最小值,而不是全局最小值。 但是,如果函数是凸函数,则可以保证它只有一个全局最小值,而没有局部最小值。 + +通常,在 ML 中,我们希望最小化几个变量 f 的实值函数:![](img/b64ba456-043e-4e33-8f2e-b9508efa63d8.png)。 几个变量的实值函数的一个简单示例是热板温度函数![](img/0b27b8e0-ff6f-4d4f-9730-ef7c4378446c.png),其在板上的坐标为![](img/81c809b6-7fe6-40b3-aa4d-a3c84510adc0.png)。 在深度学习中,我们通常最小化损失函数,该函数是多个变量(例如神经网络中的权重)的函数。 这些函数具有许多局部最小值,许多鞍点被非常平坦的区域包围,并且它们可能具有也可能没有任何全局最小值。 所有这些使得优化此类功能非常困难。 + +几个变量的函数的导数表示为偏导数,当我们更改其中一个输入变量 *x i* 时,函数将衡量函数的变化率 输入变量常量。 关于所有变量的偏导数向量称为 *f* 的**梯度向量**,用∇f 表示。 我们还可以找出函数相对于任意方向![](img/7453a5d1-4826-47f8-bff3-93e57214e1cb.png)(单位矢量)的变化速度。 这是通过在单位矢量![](img/9d284e1e-ba87-4fdf-ad26-6fa3fe11d2f0.png),即点积![](img/80cad1ea-5177-4884-9617-442850bcbf2f.png)的方向上投影梯度矢量∇f 来计算的。 这在![](img/f443e501-7c1a-4178-83ba-57a4570cac93.png)方向上被称为 *f* 的**定向导数**,通常用![](img/288daaeb-84d1-4175-8929-81e2947101a3.png)表示。 为了使 *f* 最小化,我们需要找到一个方向![](img/d482809f-0e10-45e6-8321-9859a4c068ce.png),在其中要更改![](img/30eca925-b966-4297-9627-8c75345a7cff.png),以使 *f* 的值最大程度地减小。 + +令![](img/a00a62b9-5e97-490d-97ee-800fb15a456a.png)为非常接近![](img/6e0e4adc-92fd-419f-8809-aca43ca684e1.png)的点,即![](img/3f400373-77e3-4fbe-8655-b312ddf67dc5.png)非常小。 首先,泰勒级数围绕![](img/d90cb654-5761-44ca-b03a-e253fed0e9b2.png)的阶展开式为: + +![](img/c7b1a382-402e-4cd3-8c9a-783da142e863.png) + +上式中的最后一项对于![](img/93c63280-e0c9-49ad-b444-bd4c0edded2d.png)足够接近![](img/0d2a1479-384c-4e61-b85e-ff85ae34a29f.png)可以忽略。 第二项表示 f 沿![](img/f9c99c79-4b1c-4293-9227-179a6277c4d8.png)的方向导数。 这里有: + +![](img/323c9d93-2e26-419b-9ed4-16099e6a5350.png) + +因此,如果 cos(θ)最小,则![](img/b074fe7b-5c31-4dca-97b3-b7f37a8ee172.png)最大减小,即-1,如果θ=π,即![](img/37f6f397-2a16-407c-be9f-22e1ff9df205.png)应该指向与梯度矢量,f 相反的方向,则![](img/b074fe7b-5c31-4dca-97b3-b7f37a8ee172.png)会最大程度地减小。 这是最陡下降方向:![](img/4ddc9f0b-c283-4a72-9649-82b5ae997c49.png)或**最陡梯度下降**的方向。 我们在下图中对此进行说明: + +![](img/dc2ec3d2-6f44-4905-85bf-cf64dc2599ed.png) + +热板 + +*热板*示例:给定坐标(x 和 y)上的温度由函数 *f(x,y)= 50 -y 2 -2x [ 2* 。 板在中心(0,0)处最热,温度为 50。点(x,y)处的梯度矢量由给出 *f =(-4x,-2y)*。 板上的点(2.3,2)的温度为 **40** 。 该点位于恒温轮廓上。 显然,如红色箭头所示,在与梯度相反的方向上移动,步长为ε,温度降低至 **30** 。 + +让我们使用`tensorflow`实现热板温度函数的梯度下降优化。 我们需要初始化梯度下降,所以让我们从 *x = y = 2* 开始: + +```py +import tensorflow as tf +#Initialize Gradient Descent at x,y =(2, 2) +x = tf.Variable(2, name='x', dtype=tf.float32) +y = tf.Variable(2, name='y', dtype=tf.float32) + +temperature = 50 - tf.square(y) - 2*tf.square(x) + +#Initialize Gradient Descent Optimizer +optimizer = tf.train.GradientDescentOptimizer(0.1) #0.1 is the learning rate +train = optimizer.minimize(temperature) +grad = tf.gradients(temperature, [x,y]) #Lets calculate the gradient vector + +init = tf.global_variables_initializer() +with tf.Session() as session: + session.run(init) + print("Starting at coordinate x={}, y={} and temperature there is + {}".format( + session.run(x),session.run(y),session.run(temperature))) + grad_norms = [] + for step in range(10): + session.run(train) + g = session.run(grad) + print("step ({}) x={},y={}, T={}, Gradient={}".format(step, + session.run(x), session.run(y), session.run(temperature), g)) + grad_norms.append(np.linalg.norm(g)) + +plt.plot(grad_norms) +``` + +以下是前面代码的输出。 在每个步骤中,如梯度矢量所建议的,计算`x`,`y`的新值,以使总温度最大程度地降低。 请注意,计算出的梯度与前面所述的公式完全匹配。 我们还在每个步骤之后计算梯度范数。 以下是渐变在 10 次迭代中的变化方式: + +```py +Starting at coordinate x=2.0, y=2.0 and temperature there is 38.0 +step (0) x=2.79,y=2.40000, T=28.55, Gradient=[-11.2, -4.8000002] +step (1) x=3.92,y=2.88000, T=10.97, Gradient=[-15.68, -5.7600002] +.......... +step (9) x=57.85,y=12.38347, T=-6796.81, Gradient=[-231.40375, -24.766947] +``` + +# 雅可比矩阵和黑森矩阵 + +有时,我们需要优化其输入和输出为向量的函数。 因此,对于输出向量的每个分量,我们需要计算梯度向量。 对于![](img/d2a960c4-8726-4451-b37b-87c7453cc2d0.png),我们将有 m 个梯度向量。 通过将它们排列成矩阵形式,我们得到 n x m 个偏导数![](img/b302ed2d-cd9a-4870-9f71-fc6bc31b7871.png)的矩阵,称为 **Jacobian 矩阵**。 + +对于单个变量的实值函数,如果要在某个点测量函数曲线的曲率,则需要计算在更改输入时导数将如何变化。 这称为**二阶导数**。 二阶导数为零的函数没有曲率,并且是一条平线。 现在,对于几个变量的函数,有许多二阶导数。 这些导数可以布置在称为 **Hessian 矩阵**的矩阵中。 由于二阶偏导数是对称的,即![](img/3657cb60-4e28-4e83-bd5b-324637f867ad.png),Hessian 矩阵是实对称的,因此具有实特征值。 相应的特征向量代表不同的曲率方向。 最大和最小特征值的大小之比称为黑森州的**条件数**。 它测量沿每个本征维的曲率彼此相差多少。 当 Hessian 条件数较差时,梯度下降的效果较差。 这是因为,在一个方向上,导数迅速增加,而在另一个方向上,它缓慢地增加。 渐变下降并没有意识到这一变化,因此,可能需要很长时间才能收敛。 + +对于我们的温度示例,Hessian 为![](img/96c6867c-f6a9-4daa-bb4b-aa31e80cf6a9.png)。 最大曲率的方向是最小曲率的方向的两倍。 因此,沿着 *y* 遍历,我们将更快地到达最小点。 从前面的*热板*图中所示的温度轮廓中也可以看出这一点。 + +我们可以使用二阶导数曲率信息来检查最佳点是最小还是最大。 对于单个变量, *f'(x)= 0,f''(x)> 0* 表示 *x* 是 *f* 的局部最小值, 并且 *f'(x)= 0,f''(x)< 0* 表示 *x* 是局部最大值。 这称为**二阶导数测试**(请参见下图*解释曲率*)。 类似地,对于几个变量的函数,如果 Hessian 在![](img/55302513-01d7-4f93-a61a-072c9d88ed30.png)为正定(即所有本征值均为正),则 *f* 会在![](img/02c4af7a-3e62-4c89-8779-4986116508b2.png)达到局部最小值。 如果 Hessian 在 *x* 处为负定值,则 *f* 在 *x* 处达到局部最大值。 如果 Hessian 同时具有正和负特征值,则 *x* 是 *f* 的鞍点。 否则,测试没有定论: + +![](img/25e288db-cbec-4a2b-a1dd-636f85fae3c7.png) + +解释曲率 + +存在基于使用曲率信息的二阶导数的优化算法。 牛顿法就是这样一种方法,对于凸函数,它只需一步就可以达到最佳点。 + +# 导数链规则 + +令 *f* 和 *g* 均为单个变量的实值函数。 假设 *y = g(x)*和 *z = f(g(x))= f(y)*。 + +然后,导数的链式规则指出: + +![](img/46fda9c6-12cf-4933-9a8a-14d4eb2bd6ec.png) + +同样,对于几个变量的功能,令![](img/a12ccc7c-5aea-463f-bb1c-00077b0dcb85.png),![](img/5e29eb62-35f0-419c-923a-e6d79eb07ca8.png),![](img/3375f65f-4548-482c-be23-d5047db6232d.png),然后是![](img/3e5c28bf-979d-492c-afea-53f995e924d7.png) + +因此, *z* 相对于![](img/2192626c-f160-4737-ad5a-b96f094ef411.png)和![](img/504adb56-097d-44d0-ac06-2d84988b996c.png)的梯度表示为 Jacobian ![](img/a6ac4f88-e171-4745-9939-4a40189f8c2f.png)与![](img/f2578d55-26a4-4983-9981-c24abff792e5.png)梯度向量的乘积。 因此,对于多个变量的函数,我们具有导数的链式规则,如下所示: + +![](img/03d7d7d4-46f1-4cf2-a857-d315b9503cef.png) + +神经网络学习算法由几个这样的雅可比梯度乘法组成。 + +# 随机梯度下降 + +几乎所有的神经网络学习都由一种非常重要的算法提供支持:SGD。 这是常规梯度下降算法的扩展。 在 ML 中,损失函数通常写为[示例]损失函数之和,作为*自助餐厅示例*中的平方误差 *E* 。 因此,如果我们有 *m* 个训练示例,则梯度函数也将具有 *m* 加性项。 + +梯度的计算成本随着 *m* 线性增加。 对于十亿大小的训练集,前面的梯度计算将花费很长时间,并且梯度下降算法将朝着收敛的方向非常缓慢地进行,从而在实践中无法进行学习。 + +SGD 取决于对梯度实际上是期望值的简单理解。 我们可以通过在小样本集上计算期望值来近似。 可以从训练集中随机抽取 *m'*(比 *m* 小得多的**小批量**)样本大小,并且梯度可以近似为 计算单个梯度下降步骤。 让我们再次考虑*自助餐厅示例*。 应用链式规则,误差函数(三个变量的函数)的梯度由下式给出: + +![](img/95f79b2d-3b5e-4219-bc97-62174d8ce7d2.png) + +现在,代替使用所有 *n* 训练示例来计算导数,如果我们从训练示例中抽取少量随机样本,我们仍然可以合理地近似导数。 + +gradientE 梯度给出了权重更新的估计值。 我们可以通过将其乘以一个常数ε(称为**学习率**)来进一步控制它。 取得非常高的学习率可能会增加而不是使优化目标函数值最小化。 + +在 SGD 中,在将每个小批量展示给算法后,将更新权重。 将整个训练数据一次呈现给训练算法需要很多数据点/批量大小的步骤。 一个时期描述了算法看到*整个*数据集的次数。 + +以下是自助餐厅问题的`keras`代码。 假设鱼类的实际价格为 150 美分,薯条为 50 美分,番茄酱为 100 美分。 我们已随机生成餐中物品的样品部分。 假设初始的重量/价格为每份 50 美分。 30 个纪元后,我们得到的估算值与商品的真实价格非常接近: + +```py +#The true prices used by the cashier +p_fish = 150; p_chips = 50; p_ketchup = 100 +#sample meal prices: generate data meal prices for 10 days. +np.random.seed(100) +portions = np.random.randint(low=1, high=10, size=3 ) +X = []; y = []; days = 10 +for i in range(days): + portions = np.random.randint(low=1, high=10, size=3 ) + price = p_fish * portions[0] + p_chips * portions[1] + p_ketchup * + portions[2] + X.append(portions) + y.append(price) +X = np.array(X) +y = np.array(y) + +#Create a linear model +from keras.layers import Input, Dense +from keras.models import Model +from keras.optimizers import SGD + +price_guess = [np.array([[ 50 ], [ 50],[ 50 ]]) ] #initial guess of the price +model_input = Input(shape=(3,), dtype='float32') +model_output = Dense(1, activation='linear', use_bias=False, + name='LinearNeuron', + weights=price_guess)(model_input) + sgd = SGD(lr=0.01) +model = Model(model_input, model_output) + +#define the squared error loss E stochastic gradient descent (SGD) + +optimizer +model.compile(loss="mean_squared_error", optimizer=sgd) +model.summary() +_________________________________________________________________ +Layer (type) Output Shape Param # +================================================================= +input_4 (InputLayer) (None, 3) 0 +_________________________________________________________________ +LinearNeuron (Dense) (None, 1) 3 +================================================================= +Total params: 3 +Trainable params: 3 +Non-trainable params: 0 +_________________________________________________________________ + +#train model by iterative optimization: SGD with mini-batch of size 5. + +history = model.fit(X, y, batch_size=5, epochs=30,verbose=2) + +``` + +在下图中,我们显示了学习率对迭代 SGD 算法收敛的影响: + +![](img/fcfbb0ab-e892-4392-8372-fcf9dfbaecdf.png) + +学习率对自助餐厅问题 SGD 收敛速度的影响 + +下表显示了 SGD 在 LR = 0.01 的连续时期如何更新价格猜测: + +| **时代** | **w_fish** | **w_chips** | **w_ketchup** | +| 0(初始) | 50 | 50 | 50 | +| 1 | 124.5 | 96.3 | 127.4 | +| 5 | 120.6 | 81.7 | 107.48 | +| 10 | 128.4 | 74.7 | 104.6 | +| 15 | 133.8 | 68.9 | 103.18 | +| 30 | 143.07 | 58.2 | 101.3 | +| 50 | 148.1 | 52.6 | 100.4 | + +# 非线性神经单位 + +线性神经元很简单,但是在计算上受到限制。 即使我们使用多层线性单元的深层堆栈,我们仍然具有仅学习线性变换的线性网络。 为了设计可以学习更丰富的转换集(非线性)的网络,我们需要一种在神经网络的设计中引入非线性的方法。 通过使输入的线性加权总和通过非线性函数,我们可以在神经单元中引起非线性。 + +尽管非线性函数是固定的,但是它可以通过线性单位的权重来适应数据,权重是该函数的参数。 此非线性函数称为非线性神经元的**激活函数**。 一个简单的激活函数示例是二进制阈值激活,相应的非线性单位称为 **McCulloch-Pitts 单位**。 这是一个阶跃函数,不可微分为零。 同样,在非零点,其导数为零。 其他常用的激活函数是 S 型,tanh 和 ReLu。 下图提供了这些函数的定义和图解: + +![](img/965ff81d-4930-4829-ab82-679911178735.png) + +激活函数图 + +这是激活函数定义: + +| **功能名称** | **定义** | +| 二进制阈值 | ![](img/c1d4835d-3647-4413-9da0-8e0b5ab7d2e0.png) | +| 乙状结肠 | ![](img/721c6c66-6a07-4a20-a6c0-b8db4789461e.png) | +| h | ![](img/d6ae84fd-03b3-4511-9ce7-cf04ed45a6c9.png) | +| 恢复 | ![](img/af2bf69a-947a-4c6e-a8f5-09769d6c213b.png)或![](img/7137e27c-ad6f-4d2b-a734-7add27c6a166.png) | + +如果我们有一个 k 类( *k > 2* )分类问题,那么我们基本上想学习条件概率分布 *P(y | x)*。 因此,输出层应具有 *k* 个神经元,其值应为 1。为了使网络了解所有 *k* 单位的输出应为 1, 使用 **softmax 激活**功能。 这是乙状结肠激活的概括。 像 S 型函数一样,softmax 函数将每个单元的输出压缩为 0 到 1 之间。 + +而且,它会将每个输出相除,以使输出的总和等于 1: + +![](img/099f52bb-312d-4d9b-a510-8824481123e2.png) + +数学上,softmax 函数如下所示,其中 **z** 是输出层输入的向量(如果有 10 个输出单元,则 *z* 中有 10 个元素)。 同样, *j* 索引输出单位,因此 *j = 1,2,...,K:* + +![](img/c6d124bb-1721-4458-82e8-4d805f6440b6.png) + +# 学习一个简单的非线性单位–后勤单位 + +假设我们有两个类分类问题; 也就是说,我们需要预测二进制结果变量 *y* 的值。 就概率而言,结果 *y* 取决于特征 *x* 的伯努利分布。 神经网络需要预测概率 *P(y = 1 | x)*。 为了使神经网络的输出成为有效概率,它应该位于[0,1]中。 为此,我们使用 S 型激活函数并获得非线性逻辑单元。 + +要学习逻辑单元的权重,首先我们需要一个成本函数并找到成本函数的导数。 从概率的角度来看,如果我们想最大化输入数据的可能性,则交叉熵损失会作为自然成本函数出现。 假设我们有一个训练数据集 *X = {x n ,t n }* , *n = 1* ,…, *N* ,似然函数可以写成: + +![](img/9b17fcb0-2380-4a14-a86a-bf3bf235f078.png) + +其中 *y n* 是在将 *x n* 作为输入数据传递到逻辑单元之后,S 型单元的输出。 注意![](img/08311175-d8ce-438e-85e9-2bdb6e68cb29.png)分别表示 S 型单元的目标向量(训练集中的所有 *N* 个目标值)和权重向量(所有权重的集合)。 可以通过采用似然性的负算法来定义误差函数,这给出了交叉熵代价函数: + +![](img/504e31b7-bbd1-4ae6-ae1c-a09536802838.png) + +要学习逻辑神经单元的权重,我们需要关于每个权重的输出导数。 我们将使用导数的*链规则来导出逻辑单元的误差导数:* + +让![](img/7628813a-e498-4e7a-9eaa-160d5169eebb.png) + +因此,![](img/f774189e-1543-4caf-b12e-a89dcb004d66.png) + +就线性单元的平方误差损失而言,我们发现的导数看起来与导数非常相似,但它们并不相同。 让我们仔细看一下交叉熵损失,看看它与平方误差有何不同。 我们可以如下重写交叉熵损失: + +![](img/d7ee5e5b-72b4-4115-a7e2-f5f0834b7053.png) + +* 因此,对于 *t n = 1* ,![](img/df6293d5-9713-4029-87ea-4649b69c8da4.png),但![](img/b74f5703-41a5-480e-8132-8f82b71f609c.png) +* 对于 *tn = 0* ,![](img/aa9ae558-6358-4192-8d56-93d190e0865f.png),但![](img/8f47fa4c-3de7-4308-8f14-b2613169bdcc.png) +* 也就是说,如果类别标签的预测和真实值不同,则该算法将受到很大的惩罚。 + +现在,让我们尝试将*平方误差损失*与逻辑输出一起使用。 因此,我们的成本函数为: + +![](img/67da829c-5832-4cb4-8d09-fe4617f87e1c.png) + +因此,![](img/780a5531-7c6f-4872-b6fd-dba4f22ac05f.png) + +该误差导数直接取决于 S 形函数![](img/d1c377c4-f931-40de-9351-bd1a7960efcb.png)的导数。 现在,当 *y n * 高度负值时,S 形函数趋于 0;当 *y n * 高度正值时,S 形函数趋于 1。 从 S 形曲线的平坦水平区域可以明显看出,对于 *y n * 的这种值,梯度可以缩小得太小。 因此,即使 *t n * 和 *y n * 不一致,对于这些数据点,平方误差导数也将具有很小的更新。 很多 也就是说,它们被网络严重错误分类。 这称为**消失梯度问题**。 因此,基于最大似然的交叉熵损失几乎始终是训练逻辑单元的首选损失函数。 + +# 损失函数 + +损失函数将神经网络的输出与训练中的目标值进行比较,产生一个损失值/分数,以测量网络的预测与期望值的匹配程度。 在上一节中,我们看到了针对不同任务(例如回归和二进制分类)的不同类型损失函数的需求。 以下是一些其他流行的损失函数: + +* **二进制交叉熵**:关于逻辑单元的上一节讨论的两类分类问题的对数损失或交叉熵损失。 +* **分类交叉熵**:如果我们有 *K* 类分类问题,那么我们将广义交叉熵用于 *K* 类。 +* **均方误差**:这是我们讨论过几次的均方和误差。 这广泛用于各种回归任务。 +* **平均绝对误差**:测量一组预测中误差的平均大小,而不考虑其方向。 这是测试样本中预测值与实际观测值之间的绝对差异的平均值。 **平均绝对误差**( **MAE** )对大误差赋予相对较高的权重,因为它会平方误差。 +* **平均绝对百分比误差**:以百分比形式度量误差的大小。 计算为无符号百分比误差的平均值。 使用**平均绝对百分比误差**( **MAPE** )是因为容易理解百分比。 +* **铰链损耗/平方铰链损耗**:铰链损耗用于 SVM。 他们对边际错误分类点的惩罚不同。 它们是克服交叉熵损失的好选择,并且还可以更快地训练神经网络。 对于某些分类任务,更高阶的铰链损耗(例如平方铰损耗)甚至更好。 +* **Kullback-Leibler(KL)散度**:KL 散度是一种概率分布与第二个预期概率分布之间如何偏离的度量。 + +# 数据表示 + +神经网络训练的训练集中的所有输入和目标都必须表示为张量(或多维数组)。 张量实际上是将二维矩阵推广到任意数量的维。 通常,这些是浮点张量或整数张量。 无论原始输入数据类型是什么(图像,声音,文本),都应首先将其转换为合适的张量表示形式。 此步骤称为**数据矢量化**。 以下是本书中经常使用的不同维度的张量: + +* **零维张量或标量**:仅包含一个数字的张量称为零维张量,零维张量或标量。 +* **一维张量或矢量**:包含数字数组的张量称为矢量或一维张量。 张量的维数也称为张量的**轴**。 一维张量恰好具有一个轴。 +* **矩阵(二维张量)**:包含矢量数组的张量是矩阵或二维张量。 矩阵具有两个轴(由*行*和*列*表示)。 +* **三维张量**:通过将一组(相同维度的)矩阵堆叠在一个数组中,我们得到一个三维张量。 + +通过将三维张量放置在一个数组中,可以创建三维张量。 等等。 在深度学习中,通常我们使用零维到四维张量。 + +张量具有三个关键属性: + +* 轴的尺寸或数量 +* 张量的形状,即张量在每个轴上具有多少个元素 +* 数据类型-是整数张量还是浮点型张量 + +# 张量示例 + +以下是在讨论转移学习用例时将经常使用的一些示例张量。 + +* **时间序列数据**:典型的时间序列数据将具有时间维度,并且该维度将对应于每个时间步的要素。 例如,一天中每小时的温度和湿度测量是一个时间序列数据,可以用形状为(24,2)的 2D 张量表示。 因此,数据批处理将由 3D 张量表示。 +* **图像数据**:图像通常具有三个维度:宽度,高度和颜色通道。 因此,可以用 3D 张量表示。 图像批处理由 4D 张量表示,如下图所示。 +* **视频数据**:视频由图像帧组成。 因此,要表示单个视频,我们还需要一个尺寸。 一帧是彩色图像,需要三个维度来表示一帧。 该视频由形状(帧,宽度,高度,颜色通道)的 4D 张量表示: + +![](img/e567faa7-d096-4c23-a921-00b647dced4f.png) + +* **作为张量的数据批处理**:假设我们有一批 10 张图像。 诸如 MNIST 数据中的二进制图像可以由 2D 张量表示。 对于一批 10 张图像,可以用 3D 张量表示。 此 3D 张量的第一个轴(轴= 0)称为**批次尺寸**。 + +# 张量运算 + +可以通过一组张量运算来表述用于训练/测试深度神经网络的所有计算。 例如,张量的相加,相乘和相减。 以下是本书中一些常用的张量运算: + +* **逐元素运算**:在深度学习中非常普遍地将函数独立地应用于张量的所有元素。 例如,将激活功能应用于图层中的所有单元。 其他按元素进行的运算包括将基本数学运算符(例如+,-和*)按元素进行对相同形状的两个张量进行运算。 +* **张量点**:两个张量的点积与两个张量的元素乘积不同。 两个向量的点积是一个标量,等于两个向量的元素乘积之和。 矩阵和兼容形状的向量的点积是向量,而兼容形状的两个矩阵的点积是另一个矩阵。 对于具有兼容形状的两个矩阵 x,y,我们的意思是要定义点(x,y),我们应具有 *x.shape [1] = y.shape [0]* 。 +* **广播**:假设我们要添加两个形状不同的张量。 这通常出现在神经网络的每一层。 让我们以 ReLU 层为例。 ReLU 层可以通过张量操作表示如下: *output = relu(dot(W,input)+ b)*。 在这里,我们正在计算权重矩阵与输入向量 *x* 的点积。 这将产生一个向量,然后添加一个标量偏差项。 实际上,我们希望将偏置项添加到点积输出矢量的每个元素中。 但是,偏置张量是零维张量,向量是一维。 因此,这里我们需要广播较小的张量以匹配较大张量的形状。 广播涉及两个步骤:将轴添加到较小的张量以匹配较大张量的尺寸。 然后,重复较小的张量以匹配较大张量的形状。 我们将通过一个具体示例对此进行说明:令 x 为形状(32,10),y 为形状(,10)。 我们要计算 x + y。 在广播的第一步之后,我们将轴添加到 y(较小的张量),并得到形状为 y1 的张量(1,10)。 为了匹配 x 的尺寸,我们将 y1 重复 32 次,并得到形状为(32,10)的 y2 张量。 然后,我们计算元素加法 x + y2。 +* **重塑**:张量重塑是沿轴向重新排列张量元素的操作。 重塑的一个简单示例是转置 2D 张量。 在矩阵的转置操作中,*行*和*列*互换。 重塑的另一个例子是拉紧张量。 通过将张量的所有元素沿一个轴放置,可以将多维张量重塑为矢量或一维张量。 以下是 TensorFlow 中的一些张量操作实现示例: + +```py +#EXAMPLE of Tensor Operations using tensorflow. +import tensorflow as tf + +# Initialize 3 constants: 2 vectors, a scalar and a 2D tensor +x1 = tf.constant([1,2,3,4]) +x2 = tf.constant([5,6,7,8]) +b = tf.constant(10) +W = tf.constant(-1, shape=[4, 2]) +# Elementwise Multiply/subtract +res_elem_wise_mult = tf.multiply(x1, x2) +res_elem_wise_sub = tf.subtract(x1, x2) + +#dot product of two tensors of compatable shapes +res_dot_product = tf.tensordot(x1, x2, axes=1) + +#broadcasting : add scalar 10 to all elements of the vector +res_broadcast = tf.add(x1, b) + +#Calculating Wtx +res_matrix_vector_dot = tf.multiply(tf.transpose(W), x1) + +#scalar multiplication +scal_mult_matrix = tf.scalar_mul(scalar=10, x=W) + +# Initialize Session and execute +with tf.Session() as sess: + output = sess.run([res_elem_wise_mult,res_elem_wise_sub, + res_dot_product, + res_broadcast,res_matrix_vector_dot, + scal_mult_matrix]) + print(output) +``` + +# 多层神经网络 + +单层非线性单元对于它可以学习的输入输出转换的能力仍然有限。 可以通过查看 XOR 问题来解释。 在 XOR 问题中,我们需要一个神经网络模型来学习 XOR 函数。 XOR 函数采用两个布尔输入,如果它们不同则输出 1,如果输入相同则输出 0。 + +我们可以将其视为输入模式为 *X = {(0,0),(0,1),(1,0),(1,1)}* 的模式分类问题。 第一个和第四个在类 0 中,其他在第 1 类中。让我们将此问题视为回归问题,损失为**均方误差**( **MSE** ),并尝试使用 线性单位。 通过分析求解,得出所需权重:![](img/55fd42ef-4c13-4489-9347-c211bf44fb7c.png)和偏差:![](img/17cef5d3-7769-4265-be84-58350fa79917.png)。 该模型为所有输入值输出 0.5。 因此,简单的线性神经元无法学习 XOR 函数。 + +解决 XOR 问题的一种方法是使用输入的不同表示形式,以便线性模型能够找到解决方案。 这可以通过向网络添加非线性隐藏层来实现。 我们将使用带有两个隐藏单元的 ReLU 层。 输出是布尔值,因此最适合的输出神经元是逻辑单元。 我们可以使用二进制交叉熵损失来学习权重: + +![](img/7c2d473b-3c3c-4d00-ab64-c2a35fe6dfc3.png) + +让我们使用 SGD 学习此网络的权重。 以下是 XOR 函数学习问题的`keras`代码: + +```py +model_input = Input(shape=(2,), dtype='float32') +z = Dense(2,name='HiddenLayer', kernel_initializer='ones')(model_input) +z = Activation('relu')(z) #hidden activation ReLu +z = Dense(1, name='OutputLayer')(z) +model_output = Activation('sigmoid')(z) #Output activation +model = Model(model_input, model_output) +model.summary() + +#Compile model with SGD optimization, with learning rate = 0.5 +sgd = SGD(lr=0.5) +model.compile(loss="binary_crossentropy", optimizer=sgd) +#The data set is very small - will use full batch - setting batch size = 4 +model.fit(X, y, batch_size=4, epochs=300,verbose=0) +#Output of model +preds = np.round(model.predict(X),decimals=3) +pd.DataFrame({'Y_actual':list(y), 'Predictions':list(preds)}) +``` + +前面代码的输出如下: + +![](img/b78cd483-fea2-4c10-81ef-85f419250bd9.png) + +(左)显示 4 点的原始空间-显然没有行可以将 0 类{{0,0),(1,1)}与其他类分开。 (中心)显示隐藏的 ReLU 层学习到的变换空间。 (右)该表显示了通过该功能获得的预测值 + +具有一层隐藏层的神经网络能够学习 XOR 函数。 这个例子说明了神经网络需要非线性隐藏层来做有意义的事情。 让我们仔细看一下隐藏层学习了哪些输入转换,从而使输出逻辑神经元学习该功能。 在 Keras 中,我们可以从学习的模型中提取中间隐藏层,并使用它来提取传递给输出层之前输入的转换。 上图显示了如何转换四个点的输入空间。 转换后,可以用一条线轻松地分隔 1 类和 0 类点。 这是为原始空间和变换后的空间生成图的代码: + +```py +import matplotlib.pyplot as plt + +#Extract intermediate Layer function from Model +hidden_layer_output = Model(inputs=model.input, outputs=model.get_layer('HiddenLayer').output) +projection = hidden_layer_output.predict(X) #use predict function to + extract the transformations +``` + +```py +#Plotting the transformed input + +fig = plt.figure(figsize=(5,10)) +ax = fig.add_subplot(211) +plt.scatter(x=projection[:, 0], y=projection[:, 1], c=('g')) +``` + +通过堆叠多个非线性隐藏层,我们可以构建能够学习非常复杂的非线性输入输出转换的网络。 + +# 反向传播–训练深度神经网络 + +为了训练深层的神经网络,我们仍然可以使用梯度体面/ SGD。 但是,SGD 将需要针对网络的所有权重计算损失函数的导数。 我们已经看到了如何应用导数链规则来计算逻辑单元的导数。 + +现在,对于更深的网络,我们可以逐层递归地应用相同的链规则,以获得与网络中不同深度处的层对应的权重有关的损失函数的导数。 这称为反向传播算法。 + +反向传播技术是在 1970 年代发明的,它是一种用于对复杂的嵌套函数或函数功能进行自动区分的一般优化方法。 但是,直到 1986 年,Rumelhart,Hinton 和 Williams 发表了一篇论文,标题为*通过反向传播错误学习表示法*( [https://www.iro.umontreal .ca /〜vincentp / ift3395 / lectures / backprop_old.pdf](https://www.iro.umontreal.ca/~vincentp/ift3395/lectures/backprop_old.pdf) ),该算法的重要性已为大型 ML 社区所认可。 反向传播是最早能够证明人工神经网络可以学习良好内部表示的方法之一。 也就是说,它们的隐藏层学习了非平凡的功能。 + +反向传播算法是在单个训练示例上针对每个权重计算误差导数![](img/780f07f3-072e-4f03-8501-24a3786efbe4.png)的有效方法。 为了理解反向传播算法,让我们首先代表一个带有计算图符号的神经网络。 神经网络的计算图将具有节点和有向边,其中节点代表变量(张量),边代表连接到下一个变量的变量的运算。 如果 *y = f(x)*,则变量 *x* 通过有向边连接到 *y* ,对于某些功能, *f* 。 + +例如,逻辑单元的图形可以表示如下: + +![](img/4654a29a-6aef-4c89-8ad1-e7dc98dbb64f.png) + +(左)逻辑回归作为计算图。 (右)三层网络计算图的 BP 算法信息流 + +我们用 *u 1 ,u 2 ,...,u n* 表示计算节点。 另外,我们按顺序排列节点,以便可以一个接一个地计算它们。 u n 是一个标量-损失函数。 让我们用节点![](img/8d79995a-b082-4768-9f41-24bc1876767d.png)表示网络的参数或权重。 要应用梯度下降,我们需要计算所有导数![](img/d632f776-113d-4a9f-b1ae-f66c03800e11.png)。 可以通过遵循从输入节点到最终节点![](img/8919728b-7557-44b8-8fd1-b475ea3ac5c1.png)的计算图中的有向路径来计算该图的正向计算。 这称为**前向通过**。 + +由于图中的节点为张量,因此要计算偏导数![](img/fea9ce1d-e694-44a2-97c6-8d3199f28e9c.png),将使用多个变量函数的导数链规则,该规则由雅可比矩阵与梯度的乘积表示。 反向传播算法涉及一系列这样的雅可比梯度积。 + +反向传播算法表示如下: + +1. 给定输入向量![](img/437e289f-8d33-4ea2-aafe-6c6113edfeba.png),目标向量![](img/d1691af1-2b86-419c-971e-13d4fa14d885.png),用于测量网络错误的成本函数 *C* 以及网络的初始权重集,以计算网络的前向通过并计算损耗 , *C* + +2. 向后传递-对于每个训练示例![](img/11b43902-51d6-40ce-914e-052f4d4571e9.png),针对每个层参数/权重计算损耗的导数 *C* -此步骤讨论如下: + 1. 通过对输入目标对或它们的小批量的所有梯度求平均值来组合各个梯度 + 2. 更新每个参数![](img/2390e48b-8e9b-415e-a628-158fdda7e2b1.png),α为学习率 + +我们将使用完全连接的三层神经网络解释*反向传递*。 上图显示了为此的计算图。 令 *z (i)* 表示图中的计算节点。 为了执行反向传播,导数![](img/861ffce6-b1a1-4b20-a4e8-0d7aee34152b.png)的计算将与正向传递的反向顺序完全相同。 这些由向下箭头指示。 让我们表示关于层 *l* 输入 *z (l)* * z (l)的成本函数的导数 ![](img/147cb37b-5b4e-42c8-a1a8-11cd608e32ef.png)的* 。 对于最顶层,让![](img/396956bf-0eda-471a-a398-3e739b37e214.png)。 为了递归计算,让我们考虑一个单层。 一层具有输入 *z (l)* 和输出 *z (l + 1)* 。 同样,该层将接受输入![](img/849d376a-0a44-417d-9b75-9474549e2b03.png)并产生![](img/36eaace3-e9a1-4cd0-af03-7f709279986a.png)和![](img/480965bb-c9e2-42b1-9b06-0003135d17a1.png)。 + +对于层 *l* ,![](img/05f06392-f5cc-44ff-8755-ebeded169ff2.png) + +*i* 代表梯度![](img/8caf892b-6c7d-44d5-a6d9-7203956b40b4.png)的 *i * 分量。 + +因此,我们得出了用于计算反向消息的递归公式。 使用这些,我们还可以计算关于模型参数的成本导数,如下所示: + +![](img/05544f28-196c-4245-83df-4fcf348f236d.png) + +反向传播算法在计算图中相对于其祖先 *x,*计算标量成本函数 *z* 的梯度。 该算法开始于计算关于其本身的成本 *z* 的导数![](img/4931835b-36f1-4802-8af9-b2163f218c48.png)。 可以通过将当前梯度乘以产生 *z* 的运算的雅可比行列式来计算关于 *z* 父体的梯度。 我们一直向后遍历计算图并乘以雅可比行列式,直到达到输入 *x* 为止。 + +# 神经网络学习中的挑战 + +通常,优化是一项非常困难的任务。 在本节中,我们讨论了用于训练深度模型的优化方法所涉及的一些常见挑战。 了解这些挑战对于评估神经网络模型的训练性能并采取纠正措施以缓解问题至关重要。 + +# 不适 + +矩阵的条件数是最大奇异值与最小奇异值之比。 如果条件数非常高,则矩阵是病态的,通常表示最低的奇异值比最高的奇异值小几个数量级,并且矩阵的行彼此高度相关。 这是优化中非常普遍的问题。 实际上,这甚至使凸优化问题也难以解决。 通常,神经网络会出现此问题,这会导致 SGD 卡住,即,尽管存在很强的梯度,学习也会变得非常缓慢。 对于具有良好条件数(接近 1)的数据集,误差轮廓几乎是圆形的,并且负梯度始终笔直指向误差表面的最小值。 对于条件差的数据集,错误表面在一个或多个方向上相对平坦,而在其他方向上则强烈弯曲。 对于复杂的神经网络,可能无法通过解析找到 Hessian 和病态效应。 但是,可以通过在训练时期内绘制平方梯度范数和 *g T H g* 来绘制图表,以监控疾病的影响。 + +让我们考虑我们要优化的 *f(x)*函数的二阶泰勒级数逼近。 *x 0* 点的泰勒级数由下式给出: + +![](img/28d6a247-eb12-45c2-a471-8673b8610e63.png) + +其中 *g* 是梯度向量, *H* 是 *f(x)*在 *x 0* 时的 Hessian。 如果ε是我们使用的学习率,则根据梯度下降的新点为 *x 0 -∈ g* 。 将其替换为 Taylor 系列展开式,我们得到![](img/88f6f0bb-b7d9-4a33-8d96-f46e8db8138b.png)。 + +注意,如果-∈g T g +½∈ 2 g T H g > 0,则函数的值 与 *x o* 相比,新点的值会增加。 同样,在存在强梯度的情况下,我们将具有较高的平方梯度范数![](img/1aed3478-51ba-4d72-ab7d-319d0cdf1658.png),但同时,如果其他数量为 *g T H g * 增长一个数量级,那么我们将看到 *f(x)*的下降速度非常缓慢。 但是,如果此时可以缩小学习率ε,则可能会在某种程度上使这种影响无效,因为 *g T H g * 数量乘以![](img/2167107a-9494-45ca-8029-30567768daba.png)。 可以通过在训练时期绘制平方梯度范数和 *g T H g * 来监测疾病的影响。 我们在*热板*中看到了如何计算梯度范数的示例。 + +# 局部最小值和鞍点 + +DNN 模型实质上可以保证具有极大数量的局部最小值。 如果局部最小值与全局最小值相比成本较高,则可能会出现问题。 长期以来,人们一直认为,由于存在这样的局部极小值,神经网络训练受到了困扰。 这仍然是一个活跃的研究领域,但是现在怀疑对于 DNN,大多数局部最小值具有较低的成本值,没有必要找到全局最小值,而是在权重空间中具有足够低的值的一点 成本函数。 可以通过监视梯度范数来检测强局部极小值的存在。 如果梯度范数减小到很小的数量级,则表明存在局部极小值。 + +鞍点是既不是最大值也不是最小值的点,而是被平坦区域围绕,该平坦区域的一侧目标函数值增大,而另一侧目标函数减小。 由于该平坦区域,梯度变得非常小。 然而,已经观察到,凭经验梯度下降迅速逃离了这些区域。 + +# 悬崖和爆炸梯度 + +高度非线性 DNN 的目标函数具有非常陡峭的区域,类似于悬崖,如下图所示。 在极陡峭的悬崖结构的负梯度方向上移动会使权重移得太远,以致我们完全跳下悬崖结构。 因此,在我们非常接近的时候错过了极小值。 + +因此,取消了为达到当前解决方案所做的许多工作: + +![](img/7da599f5-2250-4acf-b6ef-e8dd7d188957.png) + +解释何时需要裁剪梯度范数 + +我们可以通过裁剪渐变来避免渐变下降中的此类不良动作,也就是说,设置渐变幅度的上限。 我们记得梯度下降是基于函数的一阶泰勒近似。 这种近似在计算梯度的点附近的无穷小区域中保持良好。 如果我们跳出该区域,成本函数可能开始增加或向上弯曲。 因此,我们需要限制移动的时间。 渐变仍然可以给出大致正确的方向。 必须将更新选择为足够小,以避免越过向上的弯曲。 一种实现此目的的方法是通过设置标准的上限阈值来限制**的渐变标准**: + +![](img/2fbcbddc-c681-475e-a924-7491c6bcf2a8.png) + +在 Keras 中,可以如下实现: + +```py +#The parameters clipnorm and clipvalue can be used with all optimizers #to control gradient clipping: + +from keras import optimizers + +# All parameter gradients will be clipped to max norm of 1.0 +sgd = optimizers.SGD(lr=0.01, clipnorm=1.) +#Similarly for ADAM optmizer +adam = optimizers.Adam(clipnorm=1.) +``` + +# 初始化–目标的本地和全局结构之间的对应关系不良 + +要启动数值优化算法(例如 SGD),我们需要初始化权重。 如果我们具有目标功能,如下图所示,通过进行 SGD 建议的局部移动,我们将浪费大量时间,如果我们从真正的极小值所在的山侧开始。 在这种情况下,目标函数的局部结构不会给出任何关于最小值位于何处的提示。 可以通过适当的初始化来避免这种情况。 如果我们可以在山的另一侧的某个位置启动 SGD,则优化会快得多: + +![](img/a59194e9-3b84-4be6-b6d2-f1d2fa8c7f01.png) + +解释初始化错误的情况以及对基于梯度的优化的影响 + +# 不精确的渐变 + +大多数优化算法都是基于这样的假设,即我们在给定点具有已知的精确梯度。 但是,实际上我们只对梯度有一个估计。 这个估计有多好? 在 SGD 中,批次大小会极大地影响随机优化算法的行为,因为它确定了梯度估计的方差。 + +总之,可以通过以下四个技巧来解决神经网络训练中面临的不同问题: + +* 选择合适的学习率,可能是每个参数的自适应学习率 +* 选择合适的批次大小-梯度估计取决于此 +* 选择权重的良好初始化 +* 为隐藏层选择正确的激活功能 + +现在,让我们简要讨论一下各种启发式方法/策略,这些方法使学习 DNN 切实可行并继续使深度学习取得巨大成功。 + +# 初始化模型参数 + +以下是初始点的选择如何影响深度神经网络的迭代学习算法的性能: + +* 初始点可以确定学习是否会收敛 +* 即使学习收敛,收敛的速度也取决于起始点 +* 可比成本的初始点可能具有不同的泛化误差 + +初始化算法主要是启发式的。 良好初始化的全部要点是可以以某种方式使学习更快。 初始化的重要方面之一是*破坏初始权重集对隐藏层单位的对称性*。 如果以相同的权重对其进行初始化,则在网络相同级别上具有相同激活功能的两个单元将被同等更新。 多个单元保留在隐藏层中的原因是它们应该学习不同的功能。 因此,获得同等更新不会影响其他功能的学习。 + +打破对称性的一种简单方法是使用随机初始化-从高斯或均匀分布中采样。 模型中的偏差参数可以通过启发式选择常量。 选择权重的大小取决于优化和正则化之间的权衡。 正则化要求权重不应太大-这可能导致不良的泛化性能。 优化需要权重足够大,才能成功地通过网络传播信息。 + +# 初始化启发式 + +让我们考虑具有 *m* 输入和 *n* 输出单位的密集层: + +1. 从均匀分布![](img/b7040153-d418-490d-bbde-52337e6c9c8d.png)中采样每个重量。 + +2. Glorot 和 Bengio 建议使用统一分布初始化的规范化版本:![](img/f9f07288-c498-4754-98ab-67000c76d58c.png)。 它被设计为在每一层中具有相同的梯度变化,称为 **Glorot Uniform** 。 +3. 从平均值为 0 且方差为![](img/ee49aebe-d4f7-4d07-b7bb-d7df651b096d.png)的正态分布中采样每个权重。 这类似于 Glorot 制服,称为 **Glorot Normal** 。 +4. 对于非常大的图层,单个权重将变得非常小。 要解决此问题,另一种方法是仅初始化 *k* 非零权重。 这称为**稀疏初始化**。 +5. 将权重初始化为随机正交矩阵。 可以在初始权重矩阵上使用 Gram-Schmidt 正交化。 + +初始化方案也可以视为神经网络训练中的超参数。 如果我们有足够的计算资源,则可以评估不同的初始化方案,我们可以选择具有最佳泛化性能和更快的收敛速度的方案。 + +# SGD 的改进 + +近年来,已提出了不同的优化算法,这些算法使用不同的方程式更新模型的参数。 + +# 动量法 + +成本函数可能具有高曲率和较小但一致的梯度的区域。 这是由于 Hessian 矩阵的条件不佳以及随机梯度的方差。 SGD 在这些地区可能会放慢很多速度。 动量算法会累积先前梯度的**指数加权移动平均值**( **EWMA** ),并朝该方向移动,而不是 SGD 建议的局部梯度方向。 指数加权由超参数α∈[0,1)控制,该超参数确定先前梯度的影响衰减的速度。 动量法通过组合相反符号的梯度来阻尼高曲率方向上的振荡。 + +# 内斯特罗夫的势头 + +Nesterov 动量是动量算法的一种变体,仅在计算梯度时与动量方法不同。 标准动量法首先在当前位置计算梯度,然后在累积梯度的方向上发生较大的跳跃。 涅斯特罗夫动量首先沿先前累积的梯度的方向跃升,然后计算新点的梯度。 通过再次采用所​​有先前渐变的 EWMA 来校正新渐变: + +![](img/1cb15e3c-28b7-46d1-9475-859ca97ba73b.png) + +# 自适应学习率–每个连接均独立 + +在前面的方法中,将相同的学习率应用于所有参数更新。 由于数据稀疏,我们可能想在不同程度上更新参数。 诸如 AdaGrad,AdaDelta,RMSprop 和 Adam 之类的自适应梯度下降算法通过保持每个参数的学习率,提供了经典 SGD 的替代方法。 + +# 阿达格拉德 + +AdaGrad 算法通过按与先前所有梯度的平方和值的平方根成比例的方式将它们*与*成反比例缩放来调整每个连接的学习速率。 因此,在误差表面的平缓倾斜方向上进行了较大的移动。 但是,从一开始就采用这种技巧可能会导致某些学习率急剧下降。 但是,AdaGrad 在一些深度学习任务上仍然表现出色。 + +# RMSprop + +RMSprop 通过采用先前平方梯度的 EWMA 来修改 AdaGrad 算法。 它具有移动平均参数ρ,它控制移动平均的长度和比例。 这是深度神经网络训练最成功的算法之一。 + +# 亚当 + +**自适应力矩**( **Adam** )。 它充分利用了基于动量的算法和自适应学习率算法,并将它们组合在一起。 在此,动量算法应用于由 RMSprop 计算的重新缩放的梯度。 + +# 神经网络中的过度拟合和不足拟合 + +与其他任何 ML 训练一样,用于训练深度学习模型的数据集也分为训练,测试和验证。 在模型的迭代训练期间,通常,验证误差比训练误差略大。 如果测试误差和验证误差之间的差距随着迭代的增加而增加,则是**过拟合**的情况。 如果训练误差不再减小到足够低的值,我们可以得出结论,该模型是**不适合**的。 + +# 模型容量 + +模型的能力描述了模型可以建模的输入输出关系的复杂性。 也就是说,在模型的假设空间中允许有多大的函数集。 例如,可以将线性回归模型推广为包括多项式,而不只是线性函数。 这可以通过在构建模型时将 *x* 的 *n* 积分乘以 *x* 作为积分来完成。 还可以通过向网络添加多个隐藏的非线性层来控制模型的容量。 因此,我们可以使神经网络模型更宽或更深,或两者同时进行,以增加模型的容量。 + +但是,在模型容量和模型的泛化误差之间需要权衡: + +![](img/1fa01356-977d-41e1-ab20-36fab28171bf.png) + +(左):线性函数根据数据拟合而拟合。 (中):适合数据的二次函数可以很好地推广到看不见的点 +(右)适合数据的次数为 9 的多项式存在过度拟合的问题 + +具有极高容量的模型可能通过训练集中的学习模式而过度拟合训练集,而训练模式可能无法很好地推广到看不见的测试集。 而且,它非常适合少量的训练数据。 另一方面,低容量的模型可能难以适应训练集: + +![](img/2ade6f4c-448f-4fad-acd7-2ef73fac4d5e.png) + +在培训和验证损失方面过度拟合/不足拟合 + +# 如何避免过度拟合-正则化 + +过度拟合是 ML 中的核心问题。 对于神经网络,开发了许多策略来避免过度拟合并减少泛化误差。 这些策略统称为**正则化**。 + +# 重量分享 + +权重共享意味着同一组权重在网络的不同层中使用,因此我们需要优化的参数更少。 在一些流行的深度学习架构中可以看到这一点,例如暹罗网络和 RNN。 在几层中使用共享权重可以通过控制模型容量来更好地推广模型。 反向传播可以轻松合并线性权重约束,例如权重共享。 CNN 中使用了另一种重量分配方式,其中与完全连接的隐藏层不同,卷积层在局部区域之间具有连接。 在 CNN 中,假设可以将要由网络处理的输入(例如图像或文本)分解为具有相同性质的一组局部区域,因此可以使用相同的一组转换来处理它们。 是,共享权重。 RNN 可以被视为前馈网络,其中每个连续的层共享相同的权重集。 + +# 体重衰减 + +可以看到,像前面示例中的多项式一样,过拟合模型的权重非常大。 为了避免这种情况,可以将罚分项Ω添加到目标函数中,这将使权重更接近原点。 因此,惩罚项应该是权重范数的函数。 同样,可以通过乘以超参数α来控制惩罚项的效果。 因此我们的目标函数变为: *E(w)+αΩ(w)*。 常用的惩罚条款是: + +* **L 2 正则化**:惩罚项由![](img/4dd47053-3b9d-460f-8bd7-cf9137534599.png)给出。 在回归文献中,这称为**岭回归**。 +* **L 1** **正则化**:惩罚项由![](img/ae806812-bef5-4846-a7fd-698bdd380b0e.png)给出。 这称为**套索回归**。 + +L 1 正则化导致稀疏解; 也就是说,它会将许多权重设置为零,因此可以作为回归问题的良好特征选择方法。 + +# 早停 + +随着对大型神经网络的训练的进行,训练误差会随着时间的推移而稳步减少,但如下图所示,验证集误差开始增加,超出了某些迭代: + +![](img/14c4482c-cab1-4fa7-841f-f1130804fa87.png) + +提前停止:训练与验证错误 + +如果在验证错误开始增加的时候停止训练,我们可以建立一个具有更好泛化性能的模型。 这称为**提前停止**。 它由耐心超参数控制,该参数设置了中止训练之前观察增加的验证集错误的次数。 提前停止可以单独使用,也可以与其他正则化策略结合使用。 + +# 退出 + +辍学是一种在深度神经网络中进行正则化的计算廉价但功能强大的方法。 它可以分别应用于输入层和隐藏层。 通过在正向传递过程中将节点的输出设置为零,Dropout 随机掩盖了一部分节点的输出。 这等效于从层中删除一部分节点,并创建一个具有更少节点的新神经网络。 通常,在输入层上会删除 0.2 个节点,而在隐藏层中最多会删除 0.5 个节点。 + +**模型平均**(集成方法)在 ML 中被大量使用,通过组合各种模型的输出来减少泛化误差。 套袋是一种整体方法,其中通过从训练集中替换并随机抽样来构建 *k* 不同的数据集,并在每个模型上训练单独的 *k* 模型。 特别地,对于回归问题,模型的最终输出是 *k* 模型的输出的平均值。 还有其他组合策略。 + +还可以将 Dropout 视为一种模型平均方法,其中通过更改应用了 dropout 的基本模型的各个层上的活动节点数来创建许多模型。 + +# 批量标准化 + +在 ML 中,通常的做法是先缩放并标准化输入的训练数据,然后再将其输入模型进行训练。 对于神经网络而言,缩放也是预处理步骤之一,并且已显示出模型性能的一些改进。 在将数据馈送到隐藏层之前,我们可以应用相同的技巧吗? 批处理规范化基于此思想。 它通过减去激活的最小批量平均值μ并除以最小批量标准偏差σ来归一化前一层的激活。 在进行预测时,我们一次可能只有一个示例。 因此,不可能计算批次均值μ和批次σ。 将这些值替换为训练时收集的所有值的平均值。 + +# 我们需要更多数据吗? + +使神经模型具有更好的概括性或测试性能的最佳方法是通过训练它获得更多数据。 实际上,我们的培训数据非常有限。 以下是一些用于获取更多训练数据的流行策略: + +* **综合生成一些训练样本**:生成假训练数据并不总是那么容易。 但是,对于某些类型的数据,例如图像/视频/语音,可以将转换应用于原始数据以生成新数据。 例如,可以平移,旋转或缩放图像以生成新的图像样本。 +* **带噪声的训练**:将受控随机噪声添加到训练数据是另一种流行的数据增强策略。 噪声也可以添加到神经网络的隐藏层中。 + +# 神经网络的超参数 + +神经网络的体系结构级参数,例如隐藏层数,每个隐藏层的单位数,以及与训练相关的参数,例如学习率,优化器算法,优化器参数-动量,L1 / L2 正则化器和 辍学统称为神经网络的**超参数**。 神经网络的权重称为神经网络的**参数**。 一些超参数影响训练算法的时间和成本,而一些影响模型的泛化性能。 + +# 自动超参数调整 + +开发了多种用于超参数调整的方法。 但是,对于大多数参数,需要为每个超参数指定一个特定范围的值。 可以通过了解它们对模型容量的影响来设置大多数超参数。 + +# 网格搜索 + +网格搜索是对超参数空间的手动指定子集的详尽搜索。 网格搜索算法需要性能指标,例如交叉验证错误或验证集错误,以评估最佳可能参数。 通常,网格搜索涉及选择对数标度的参数。 例如,可以从集合{50、100、200、500、1000,...}中选择在集合{0.1、0.01、0.001、0.0001}内获得的学习率或多个隐藏单元。 网格搜索的计算成本随着超参数的数量呈指数增长。 因此,另一种流行的技术是随机网格搜索。 随机搜索从所有指定的参数范围中对参数采样固定次数。 当我们具有高维超参数空间时,发现这比穷举搜索更有效。 更好,因为可能存在一些不会显着影响损耗的超参数。 + +# 摘要 + +在本章中,我们涉及了深度学习的基础知识。 我们真的赞扬您为实现这一目标所做的努力! 本章的目的是向您介绍与深度学习领域有关的核心概念和术语。 我们首先简要介绍了深度学习,然后介绍了当今深度学习领域中流行的框架。 还包括详细的分步指南,用于设置您自己的深度学习环境,以在 GPU 上开发和训练大规模深度学习模型。 + +最后,我们涵盖了围绕神经网络的基本概念,包括线性和非线性神经元,数据表示,链规则,损失函数,多层网络和 SGD。 还讨论了神经网络中的学习挑战,包括围绕局部极小值和爆炸梯度的常见警告。 我们研究了神经网络中过拟合和欠拟合的问题,以及处理这些问题的策略。 然后,我们介绍了神经网络单元的流行初始化启发法。 除此之外,我们还探索了一些更新的优化技术,它们是对香草 SGD 的改进,其中包括 RMSprop 和 Adam 之类的流行方法。 + +在下一章中,我们将探讨深度学习模型周围的各种体系结构,这些体系结构可用于解决不同类型的问题。 \ No newline at end of file diff --git a/docs/handson-tl-py/3.md b/docs/handson-tl-py/3.md new file mode 100644 index 0000000000000000000000000000000000000000..a2716d9d15f5e626758b434877221d9bd5abc6b7 --- /dev/null +++ b/docs/handson-tl-py/3.md @@ -0,0 +1,578 @@ +# 了解深度学习架构 + +本章将着重于理解当今深度学习中存在的各种架构。 神经网络的许多成功都在于对神经网络架构的精心设计。 自 1960 年代的传统**人工神经网络**( **ANNs** )以来,我们已经走了很长一段路。 在本书中,我们介绍了基本模型架构,例如完全连接的深度神经网络,**卷积神经网络**( **CNN** ),**递归神经网络**( **RNN** ),**长短期记忆**( **LSTM** )网络,以及最新的胶囊网络。 + +在本章中,我们将介绍以下主题: + +* 为什么神经网络架构设计很重要 +* 各种流行的架构设计和应用 + +# 神经网络架构 + +**构架**一词是指神经网络的整体结构,包括其可以具有多少层以及各层中的单元应如何相互连接(例如,连续层中的单元可以完全连接) ,部分连接,或者甚至可以完全跳过下一层,然后再连接到网络中更高级别的一层。 随着模块化深度学习框架(例如 Caffe,Torch 和 TensorFlow)的出现,复杂的神经网络设计发生了革命性的变化。 现在,我们可以将神经网络设计与 Lego 块进行比较,在这里您可以构建几乎可以想象的任何结构。 但是,这些设计不仅仅是随机的猜测。 这些设计背后的直觉通常是由设计人员对问题的领域知识以及一些反复试验来精调最终设计的结果所驱动。 + +# 为什么需要不同的架构 + +前馈多层神经网络具有学习巨大的假设空间并提取每个非线性隐藏层中复杂特征的能力。 那么,为什么我们需要不同的架构? 让我们尝试理解这一点。 + +特征工程是**机器学习**( **ML** )中最重要的方面之一。 如果功能太少或不相关,则可能会导致拟合不足。 而且功能太多,可能会使数据过拟合。 创建一组好的手工制作的功能是一项繁琐,耗时且重复的任务。 + +深度学习带有一个希望,即给定足够的数据,深度学习模型能够自动确定正确的功能集,即复杂性不断增加的功能层次。 好吧,深度学习的希望是真实的,并且会产生误导。 深度学习确实在许多情况下简化了功能工程,但是它并没有完全消除对它的需求。 随着手动特征工程的减少,神经网络模型本身的体系结构变得越来越复杂。 特定体系结构旨在解决特定问题。 与手工特征工程相比,建筑工程是一种更为通用的方法。 在架构工程中,与要素工程不同,领域知识不会硬编码为特定要素,而只会在抽象级别使用。 例如,如果我们要处理图像数据,则有关该数据的一个非常高级的信息是对象像素的二维局部性,而另一个是平移不变性。 换句话说,将猫的图像平移几个像素仍然可以保持猫的状态。 + +在特征工程方法中,我们必须使用非常具体的特征(例如边缘检测器,拐角检测器和各种平滑滤波器)来为任何图像处理/计算机视觉任务构建分类器。 现在,对于神经网络,我们如何编码二维局部性和翻译不变性信息? 如果将密集的完全连接层放置在输入数据层之前,则图像中的每个像素都将连接到密集层中的每个单元。 但是,来自两个空间遥远对象的像素不必连接到同一隐藏单元。 经过长时间的大量数据训练后,具有很强的 L1 正则化能力的神经网络可能能够稀疏权重。 我们可以设计体系结构以仅将本地连接限制到下一层。 少量的相邻像素(例如,像素的 10 x 10 子图像)可能具有与隐藏层的一个单元的连接。 由于转换不变,因此可以重用这些连接中使用的权重。 CNN 就是这样做的。 这种权重重用策略还有其他好处,例如大大减少了模型参数的数量。 这有助于模型进行概括。 + +让我们再举一个例子,说明如何将抽象领域知识硬编码到神经网络中。 假设我们有时间数据或顺序数据。 正常的前馈网络会将每个输入示例视为独立于先前的输入。 因此,学习到的任何隐藏特征表示也应取决于数据的最近历史,而不仅仅是当前数据。 因此,神经网络应该具有一些反馈回路或记忆。 这个关键思想产生了递归神经网络体系结构及其现代的强大变体,例如 LSTM 网络。 + +其他高级 ML 问题(例如语音翻译,问题解答系统和关系建模)要求开发各种深度学习体系结构。 + +# 各种架构 + +现在让我们看一些流行的神经网络体系结构及其应用。 我们将从**多层感知器**( **MLP** )网络开始。 我们已经介绍了单层感知器网络,这是最基本的神经网络体系结构。 + +# MLP 和深度神经网络 + +**MLP** 或简单的**深层神经网络**( **DNNs** )是神经网络架构的最基本形式。 神经单元一层又一层地排列,相邻的网络层彼此完全连接。 我们已经在上一章中对此进行了详细讨论: + +![](img/12eeeba8-9883-4473-a839-66a9d7304c32.png) + +# 自动编码器神经网络 + +自动编码器通常用于减少神经网络中数据的维数。 自动编码器也已成功用于异常检测和新颖性检测问题。 **一个** **utoencoder** **神经网络**属于无监督学习类别。 在此,目标值设置为等于输入值。 换句话说,我们想学习身份功能。 通过这样做,我们可以获得数据的紧凑表示。 + +通过最小化输入和输出之间的差异来训练网络。 典型的自动编码器体系结构是 DNN 体系结构的略微变体,其中,每个隐藏层的单位数量逐渐减少,直到某个点,然后逐渐增加,最终层尺寸等于输入尺寸。 其背后的关键思想是在网络中引入瓶颈,并迫使其学习有意义的紧凑表示形式。 隐藏单元的中间层(瓶颈)基本上是输入的降维编码。 隐藏层的前半部分称为**编码器**,后半部分称为**解码器**。 下面描述了一个简单的自动编码器体系结构。 名为 **z** 的层是此处的表示层: + +![](img/39e61d86-4db6-460c-a13b-8da4dbcd4889.png) + +资料来源:https://cloud4scieng.org/manifold-learning-and-deep-autoencoders-in-science/ + +# 变体自动编码器 + +深度很深的自动编码器很难训练,并且容易过度安装。 有许多改进了自动编码器训练方式的开发,例如使用**受限玻尔兹曼机**( **RBM** )进行生成式预训练。 **变分自编码器**( **VAE** )也是生成模型,与其他深层生成模型相比,VAE 在计算上易于处理且稳定,可以通过有效的反向传播算法进行估算。 它们受到贝叶斯分析中变分推理的启发。 + +变分推理的概念如下:给定输入分布 *x* 时,输出 *y* 上的后验概率分布太复杂而无法使用。 因此,让我们用一个更简单的分布 *q(y)*来近似复杂的后验 *p(y | x)*。 在这里, *q* 是从最接近后验的分布族 *Q* 中选择的。 例如,此技术用于训练**潜在 Dirichlet 分配**( **LDA** )(它们对文本进行主题建模,并且是贝叶斯生成模型)。 但是,经典变分推论的一个关键局限性是需要对似然性和先验共轭才能进行优化。 VAE 引入了使用神经网络来输出条件后验的方法(Kingma 和 Welling,2013 年),从而允许使用**随机梯度下降**( **SGD** )和反向传播来优化变分推断目标。 。 该方法称为**重新参数化技巧**。 + +给定数据集 *X* ,VAE 可以生成与样本 X 类似但不一定相等的新样本。数据集 X 具有 *N* **独立且完全相同的**([HTG6 连续或离散随机变量 *x* 的样本。 假设数据是通过某种随机过程生成的,涉及一个未观察到的连续随机变量 *z* 。 在简单自动编码器的此示例中,变量 *z* 是确定性的,并且是随机变量。 数据生成是一个两步过程: + +1. *z* 的值是根据先验分布生成的,*ρθ(z)* +2. 根据条件分布生成 *x* 的值,*ρθ(x | z)* + +因此, *p(x)*基本上是边际概率,计算公式为: + +![](img/b2a7d0c1-ab50-49bf-8b01-9b77ea936dda.png) + +分布的参数*θ*和潜变量 *z* 都是未知的。 在此, *x* 可以通过从边际 *p(x)*取样来生成。 反向传播无法处理网络中的随机变量 *z* 或随机层 *z* 。 假设先验分布 *p(z)*为高斯分布,我们可以利用高斯分布的*位置尺度*属性,并将随机层重写为 *z =μ+ σε*,其中μ是位置参数,σ是刻度,ε是白噪声。 现在,我们可以获得噪声ε,的多个样本,并将它们作为确定性输入提供给神经网络。 + +然后,该模型成为端到端确定性深度神经网络,如下所示: + +![](img/0cf59473-54cf-44b5-a5d3-36699ee1e65c.png) + +在这里,解码器部分与我们之前介绍的简单自动编码器的情况相同。 训练此网络的损失函数如何? 因为这是一个概率模型,所以最直接的方法是通过边际 *p(x)*的最大似然来推导损失函数。 但是,该函数在计算上变得难以处理。 这样,我们将变分推理技术应用于下限 *L* 。 导出边际似然,然后通过最大化下限 *L* 导出损失函数。 可以在 Kingma 及其合作者的论文*自动编码变异贝叶斯*,ICLR,2014 年[ [https://arxiv.org/abs/1312.6114](https://arxiv.org/abs/1312.6114) )。 VAE 已成功应用于各个领域。 例如,文本的深层语义哈希是由 VAE 完成的,其中将文本文档转换为二进制代码。 同样,相似的文档具有相似的二进制地址。 这样,这些代码可用于更快,更有效的检索,以及文档的聚类和分类。 + +# 生成对抗网络 + +自从 Ian Goodfellow 及其合著者在 2014 年 NIPS 论文中首次引入以来,**生成对抗网络**( **GAN** )就广受青睐( [https:// arxiv .org / pdf / 1406.2661.pdf](https://arxiv.org/pdf/1406.2661.pdf) )。 现在我们看到了 GAN 在各个领域的应用。 Insilico Medicine 的研究人员提出了一种使用 GAN 进行人工药物发现的方法。 他们还发现了在图像处理和视频处理问题中的应用,例如图像样式转换和**深度卷积生成对抗网络**( **DCGAN** )。 + +顾名思义,这是使用神经网络的另一种生成模型。 GAN 具有两个主要组成部分:生成器神经网络和鉴别器神经网络。 生成器网络采用随机噪声输入,并尝试生成数据样本。 鉴别器网络将生成的数据与真实数据进行比较,并使用 S 型输出激活来解决生成的数据是否为伪造的二进制分类问题。 生成器和鉴别器都在不断竞争,并试图互相愚弄-这就是 GAN 也被称为**对抗网络**的原因。 这种竞争驱使两个网络都提高其权重,直到鉴别器开始输出 0.5 的概率为止。 也就是说,直到生成器开始生成真实图像为止。 通过反向传播同时训练两个网络。 这是 GAN 的高级结构图: + +![](img/e1a9d83e-820d-491b-8427-d1068af52a1e.png) + +训练这些网络的损失函数可以定义如下。 令 *pdata* 为数据的概率分布, *pg* 为生成器分布。 *D(x)*表示 *x* 来自 *pdata* 而非来自 *pg* 的概率。 对 *D* 进行了培训,以使将正确标签分配给 *G* 的训练示例和样本的概率最大化。 同时,训练 *G* 以最小化 *log(1- D(G(z)))*。 因此, *D* 和 *G* 玩一个具有值函数 *V(D,G)*的两人 minimax 游戏: + +![](img/dc4229cc-0d2e-44a0-99a8-b6792459c1d2.png) + +可以证明,对于 *p g = p 数据* 来说,这种极小极大游戏具有全局最优性。 + +以下是通过反向传播训练 GAN 以获得所需结果的算法: + +```py +for N epochs do: + + #update discriminator net first + + for k steps do: + + Sample minibatch of m noise samples {z(1), , . . . , z + (m)} from noise prior pg(z). + + Sample minibatch of m examples {x(1), . . . , x(m)} from + data generating distribution pdata(x). + + Update the discriminator by: + + end for + + Sample minibatch of m noise samples {z(1) , . . . , z (m)} + from noise prior pg(z). + Update the generator by descending its stochastic gradient: + +end for +``` + +# 使用 GAN 架构的文本到图像合成 + +让我们看看使用 GAN 从文字描述生成图像。 下图显示了这种 GAN 的完整体系结构: + +![](img/bdfbe2ec-0f06-47ee-b805-057f3369231b.png) + +这是条件 GAN 的一种。 生成器网络获取带有噪声矢量的输入文本以生成图像。 生成的图像以输入文本为条件。 使用嵌入层*φ(t)*将图像描述转换为密集向量。 使用完全连接的层对其进行压缩,然后将其与噪声矢量连接。 检测器网络是 CNN,并且生成器网络的体系结构使用具有与 CNN 网络中使用的过滤器相同的过滤器的反卷积层。 反卷积基本上是转置的卷积,我们将在后面讨论。 + +# CNN + +CNN 是专门设计用于识别形状图案的多层神经网络,这些形状图案对于二维图像数据的平移,缩放和旋转具有高度不变性。 这些网络需要以监督的方式进行培训。 通常,提供一组标记的对象类(例如 MNIST 或 ImageNet)作为训练集。 任何 CNN 模型的关键都在于卷积层和子采样/合并层。 因此,让我们详细了解在这些层中执行的操作。 + +# 卷积运算符 + +CNN 背后的中心思想是**卷积**的数学运算,这是一种特殊的线性运算。 它广泛用于物理,统计,计算机视觉以及图像和信号处理等各个领域。 为了理解这一点,让我们从一个例子开始。 *嘈杂的*激光传感器正在跟踪飞船的位置。 为了更好地估计飞船的位置,我们可以取几个读数的平均值,对最近的观测结果给予更多的权重。 令 *x(t)*代表时间位置, *t* ,令 *w(t)*为加权函数。 + +该职位的估算可以写成: + +![](img/482af651-725c-47b7-916b-3e2347490314.png) + +这里,权重函数 *w(t)*被称为卷积的**内核**。 我们可以使用卷积计算位置传感器数据的**简单移动平均值**( **SMA** )。 令 *m* 为 SMA 的窗口大小。 + +内核定义为: + +![](img/3ea03e05-e414-414b-a22f-a33b8cb39f64.png) + +这是使用卷积的 SMA 的 NumPy 实现: + +```py +x = [1, 2, 3, 4, 5, 6, 7] +m = 3 #moving average window size +sma = np.convolve(x, np.ones((m,))/m, mode='valid') +#Outputs +#array([ 2., 3., 4., 5., 6.]) +``` + +在深度学习中,输入通常是多维数据数组,而内核通常是由训练算法学习的参数多维数组。 尽管我们在卷积公式中具有无限求和,但对于实际实现而言,权重函数的值仅在值的有限子集时才为非零(如 SMA 的情况)。 因此,公式中的求和变为有限求和。 卷积可应用于多个轴。 如果我们有一个二维图像 *I* 和一个二维平滑核 *K* ,则卷积图像的计算方式如下: + +![](img/f1df58da-ec48-4fda-828a-1979a036e221.png) + +或者,也可以如下计算: + +![](img/3401841c-2937-4b3b-9e6a-d565df408d9e.png) + +下图说明了如何使用大小为 2 且步幅为 1 的内核来计算卷积层输出: + +![](img/11fdb71c-0ba2-48d1-a1ba-a28381d1a907.png) + +# 卷积中的跨步和填充模式 + +卷积内核通过一次移动一列/行来围绕输入体积进行卷积。 滤波器移位的量称为**跨度**。 在前面的场景中,将跨度隐式设置为 1。如果将内核跨度移动 2(两列或两行),则输出单位的数量将减少: + +![](img/a65a1d73-ae70-45c8-b643-0b27aa9fe394.png) + +卷积运算符减小了输入的大小。 如果要保留输入的大小,则需要在输入周围均匀填充零。 对于二维图像,这意味着在图像的四个侧面周围添加零像素的边框。 边框的粗细(即添加的像素行数)取决于所应用的内核大小。 任何卷积运算符实现通常都采用指定填充类型的模式参数。 有两个这样的参数: + +* `SAME`:指定输出大小与输入大小相同。 这要求过滤器窗口滑到输入图的外部,因此需要填充。 +* `VALID`:指定过滤器窗口停留在输入图内的有效位置,因此输出大小缩小`filter_size`减一。 没有填充发生。 + +在前面的一维卷积码中,我们将模式设置为`VALID`,因此没有填充发生。 您可以尝试使用*相同的*填充。 + +# 卷积层 + +卷积层包括三个主要阶段,每个阶段在多层网络上都构成一些结构约束: + +* **特征提取**:每个单元都从上一层的本地接受域建立连接,从而迫使网络提取本地特征。 如果我们有一个 32 x 32 的图像,并且接收区域的大小为 4 x 4,则一个隐藏层将连接到先前层中的 16 个单元,而我们总共将拥有 28 x 28 个隐藏单元。 因此,输入层与隐藏层建立了 28 x 28 x 16 的连接,这是这两层之间的参数数(每个连接的权重)。 如果它是一个完全连接的密集隐藏层,则将有 32 x 32 x 28 x 28 个参数。 因此,在这种体系结构约束下,我们大大减少了参数数量。 现在,此局部线性激活的输出通过非线性激活函数(例如 ReLU)运行。 该阶段有时称为**检测器阶段**。 一旦学习了特征检测器,只要保留其相对于其他特征的位置,该特征在看不见的图像中的确切位置就不重要了。 与隐藏神经元的感受野相关的突触权重是卷积的核心。 +* **特征映射**:特征检测器创建平面(绿色平面如下所示)形式的特征图。 为了提取不同类型的局部特征并具有更丰富的数据表示,并行执行多个卷积以产生多个特征图,如下所示: + +![](img/51b9fe99-1a68-45a1-87b6-3c24c4ce603c.png) + +* **通过合并**进行二次采样:这是由计算层完成的,该计算层通过用附近单元的摘要统计替换某些位置的特征检测器单元来对特征检测器的输出进行二次采样。 摘要统计信息可以是最大值或平均值。 此操作降低了将特征图输出到简单失真(例如线性移位和旋转)的敏感性。 池引入不变性: + +![](img/3cf0fe3d-bbc6-41cb-ac42-589a9a9d8c53.png) + +将这三个阶段结合在一起,就可以为我们提供 CNN 中的一个复杂层,这三个阶段中的每个本身就是简单层: + +![](img/b6ac6afe-c059-42a3-89cb-dbd4f5e81844.png) + +可以通过将并排堆叠在一起的方式,将合并后的要素图按一个体积进行排列,如下所示。 然后,我们可以再次对此应用下一个卷积级别。 现在,单个特征图中隐藏单元的接受场将是神经单元的体积,如下图所示。 但是,将在整个深度上使用同一组二维权重。 深度尺寸通常由通道组成。 如果我们有 RGB 输入图像,那么我们的输入本身将具有三个通道。 但是,卷积是二维应用的,并且所有通道之间的权重相同: + +![](img/d6e194c3-df63-489c-94bd-527a19d1783f.png) + +# LeNet 架构 + +这是 LeCun 及其合作者于 1998 年设计的具有开创性的七级卷积网络,用于数字分类。 后来,它被几家银行用来识别支票上的手写数字。 网络的较低层由交替的卷积和最大池化层组成。 + +上层是完全连接的密集 MLP(由隐藏层和逻辑回归组成)。 第一个完全连接的图层的输入是上一层的所有要素图的集合: + +![](img/0cff1c62-e1c0-40ec-b7a8-cad1f3da34b0.png) + +在将 CNN 成功应用于数字分类之后,研究人员将重点放在构建可以对 ImageNet 图像进行分类的更复杂的体系结构上。 ImageNet 是根据 WordNet 层次结构(目前仅是名词的层次结构)组织的图像数据库,其中层次结构的每个节点都由数百或数千个图像表示。 ImageNet 项目每年举办一次软件竞赛 **ImageNet 大规模视觉识别挑战赛**( **ILSVRC** ),该竞赛将大规模评估目标检测和图像分类的算法。 评估标准是前五名/最高分。 通过从最终的致密 softmax 层获取 CNN 的预测来计算这些值。 如果目标标签是前五个预测之一(概率最高的五个预测),则认为它是成功的。 前五名得分是通过将预测标签(位于前 5 名)与目标标签匹配的时间除以所评估图像的数量得出的。 + +最高得分的计算方法与此类似。 + +# 亚历克斯网 + +在 2012 年,AlexNet 的表现明显优于所有先前的竞争对手,并通过将前 5 名的错误降低到 15.3%赢得了 ILSVRC,而亚军则只有 26%。 这项工作推广了 CNN 在计算机视觉中的应用。 AlexNet 与 LeNet 的体系结构非常相似,但是每层具有更多的过滤器,并且更深入。 而且,AlexNet 引入了使用堆栈式卷积的方法,而不是始终使用替代性卷积池。 小卷积的堆栈优于大卷积层的接收场,因为这会引入更多的非线性和更少的参数。 + +假设我们彼此之间具有三个 3 x 3 卷积层(在它们之间具有非线性或池化层)。 在此,第一卷积层上的每个神经元都具有输入体积的 3 x 3 视图。 第二卷积层上的神经元具有第一卷积层的 3 x 3 视图,因此具有输入体积的 5 x 5 视图。 类似地,第三卷积层上的神经元具有第二卷积层的 3 x 3 视图,因此具有输入体积的 7 x 7 视图。 显然,与 3 个 3 x 3 卷积的 3 x(3 x 3)= 27 个参数相比,7 x 7 接收场的参数数量是 49 倍。 + +# ZFNet + +2013 年 ILSVRC 冠军是 Matthew Zeiler 和 Rob Fergus 的 CNN。 它被称为 **ZFNet** 。 通过调整体系结构的超参数,特别是通过扩展中间卷积层的大小并减小第一层的步幅和过滤器大小,它在 AlexNet 上得到了改进,从 AlexNet 的 11 x 11 步幅 4 变为 7x 7 步幅。 ZFNet。 这背后的直觉是,在第一卷积层中使用较小的滤镜尺寸有助于保留大量原始像素信息。 此外,AlexNet 接受了 1500 万张图像的培训,而 ZFNet 接受了 130 万张图像的培训: + +![](img/4801ec05-89b1-43d2-ad9f-32b0d98b140e.png) + +# GoogLeNet(起始网络) + +2014 年 ILSVRC 获奖者是来自 Google 的名为 **GoogLeNet** 的卷积网络。 它的前 5 个错误率达到 6.67%! 这非常接近人类水平的表现。 排在第二位的是来自 Karen Simonyan 和 Andrew Zisserman 的网络,称为 **VGGNet** 。 GoogLeNet 使用 CNN 引入了一个称为**初始层**的新架构组件。 初始层背后的直觉是使用较大的卷积,但对于图像上的较小信息也要保持较高的分辨率。 + +因此,我们可以并行处理不同大小的内核,从 1 x 1 到更大的内核,例如 5 x 5,然后将输出级联以产生下一层: + +![](img/cf82886a-ab82-4903-a258-0b84f3f27b68.png) + +显然,增加更多的图层会爆炸参数空间。 为了控制这一点,使用了降维技巧。 请注意,1 x 1 卷积基本上不会减小图像的空间尺寸。 但是,我们可以使用 1 x 1 滤镜减少特征图的数量,并减少卷积层的深度,如下图所示: + +![](img/d71299bb-48ef-4a93-9098-e0aa50229f51.png) + +下图描述了完整的 GoogLeNet 架构: + +![](img/bc08ac4f-958f-4283-b679-35c221d7292a.png) + +# VGG + +牛津视觉几何学组或简称为 **VGG** 的研究人员开发了 VGG 网络,该网络的特点是简单,仅使用 3 x 3 卷积层并排叠加,且深度不断增加。 减小卷大小由最大池化处理。 最后,两个完全连接的层(每个层有 4,096 个节点)之后是 softmax 层。 对输入进行的唯一预处理是从每个像素减去在训练集上计算出的 RGB 平均值。 + +通过最大池化层执行池化,最大池化层跟随一些卷积层。 并非所有卷积层都跟随最大池化。 最大合并在 2 x 2 像素的窗口上执行,步幅为 2。每个隐藏层都使用 ReLU 激活。 在大多数 VGG 变体中,过滤器的数量随深度的增加而增加。 下图显示了 16 层体系结构 VGG-16。 下一节显示了具有均匀 3 x 3 卷积(VGG-19)的 19 层体系结构。 VGG 模型的成功证实了深度在图像表示中的重要性: + +![](img/4429ffc8-edae-4456-be09-62b672c01130.png) + +VGG-16:输入大小为 224 x 224 x 3 的 RGB 图像,每层中的滤镜数量都被圈起来 + +# 残留神经网络 + +在 ILSVRC 2015 中,由 Kaiming He 及其来自 Microsoft Research Asia 的合著者介绍了一种具有*跳过连接*和*批量归一化*的新颖 CNN 架构,称为**残留神经网络** ]( **ResNet** )。 这样,他们就可以训练一个具有 152 层(比 VGG 网络深八倍)的神经网络,同时仍比 VGG 网络具有更低的复杂度。 它的前 5 个错误率达到 3.57%,在此数据集上超过了人类水平的性能。 + +该体系结构的主要思想如下。 他们没有希望一组堆叠的层将直接适合所需的基础映射 *H(x)*,而是尝试适应残差映射。 更正式地讲,他们让堆叠的层集学习残差 *R(x)= H(x)-x* ,随后通过跳过连接获得真实映射。 然后将输入添加到学习的残差 *R(x)+ x* 。 + +同样,在每次卷积之后和激活之前立即应用批处理归一化: + +![](img/1682c55a-64fa-4819-8159-98e2f3f4887f.png) + +剩余网络的一个组成部分 + +与 VGG-19 相比,这是完整的 ResNet 体系结构。 点缀的跳过连接显示尺寸增加; 因此,为了使添加有效,不执行填充。 同样,尺寸的增加由颜色的变化表示: + +![](img/e8526d3b-0776-40c7-8c03-aa7933a3889c.png) + +到目前为止,我们已经讨论过的 CNN 模型的所有变体都可以在 Keras 和 TensorFlow 中作为预训练模型使用。 我们将在我们的迁移学习应用程序中大量使用它们。 这是用于加载各种 VGG 模型的 Keras 代码段。 可以在 [https://keras.io/applications/](https://keras.io/applications/) 中找到更多内容: + +```py +from keras.applications.vgg16 import VGG16 +model = VGG16() +print(model.summary()) +``` + +# 胶囊网络 + +我们已经讨论了各种 CNN 架构是如何演变的,并且已经研究了它们的连续改进。 现在我们可以将 CNN 用于更高级的应用程序,例如**高级驾驶员辅助系统**( **ADAS** )和自动驾驶汽车吗? 我们能否在现实世界中实时地检测道路上的障碍物,行人和其他重叠物体? 也许不会! 我们还不在那里。 尽管 CNN 在 ImageNet 竞赛中取得了巨大成功,但 CNN 仍然存在一些严重的局限性,将它们的适用性限制在更高级的现实问题中。 CNN 的翻译不变性差,并且缺乏有关方向的信息(或*姿势*)。 + +姿势信息是指相对于观看者的三维方向,还指照明和颜色。 旋转物体或改变照明条件时,CNN 确实会带来麻烦。 根据 Hinton 的说法,CNN 根本无法进行*顺手性*检测; 例如,即使他们都接受过这两种训练,他们也无法从右鞋中分辨出左鞋。 CNN 受到这些限制的原因之一是使用最大池化,这是引入不变性的粗略方法。 通过粗略不变性,我们的意思是,如果图像*稍有*移位/旋转,则最大合并的输出不会发生太大变化。 实际上,我们不仅需要不变性,还需要**等价性**; 即,在图像的**对称** **变换**下的不变性。 + +边缘检测器是 CNN 中的第一层,其功能与人脑中的视觉皮层系统相同。 大脑和 CNN 之间的差异出现在较高水平。 有效地将低层视觉信息路由到高层信息,例如各种姿势和颜色或各种比例和速度的对象,这是由皮质微柱完成的,Hinton 将其命名为*胶囊*。 这种*路由*机制使人的视觉系统比 CNN 更强大。 + +**胶囊网络**( **CapsNets** )对 CNN 架构进行了两个基本更改:首先,它们用矢量输出胶囊代替 CNN 的标量输出特征检测器; 其次,他们将最大池与按协议路由一起使用。 这是一个简单的 CapsNet 架构: + +![](img/86ce06a7-a636-494c-b62f-1addb7e0d1a3.png) + +这是一种浅薄的体系结构,需要根据 MNIST 数据(28 x 28 个手写数字图像)进行训练。 这具有两个卷积层。 第一卷积层具有 256 个特征图,具有 9 x 9 内核(步幅= 1)和 ReLu 激活。 因此,每个特征图是(28-9 + 1)x(28-9 + 1)或 20 x20。第二个卷积层又具有 256 个特征图,具有 9 x 9 个内核(步幅= 2)和 ReLu 激活。 这里每个特征图是 6 x 6、6 =((20-9)/ 2 + 1)。 对该图层进行了重塑,或者将特征图重新分组为 32 组,每组具有 8 个特征图(256 = 8 x 32)。 分组过程旨在创建每个大小为 8 的特征向量。为了表示姿势,向量表示是一种更自然的表示。 来自第二层的分组特征图称为**主胶囊**层。 我们有(32 x 6 x 6)八维胶囊向量,其中每个胶囊包含 8 个卷积单元,内核为 9 x 9,步幅为 2。最后一个胶囊层( **DigitCaps** )有十六个 每位类别(10 个类别)的三维胶囊,这些胶囊中的每一个都从主胶囊层中的所有胶囊接收输入。 + +胶囊的输出矢量的长度表示由胶囊表示的实体存在于当前输入中的概率。 胶囊向量的长度被归一化,并保持在 0 到 1 之间。此外,在向量的范数上使用了压缩函数,以使短向量缩小到几乎零长度,而长向量缩小到略小于 1 的长度。 + +压扁功能为: + +![](img/f9d8bcc5-8454-4231-a9eb-defa4e9761d3.png) + +其中 *x* 是向量的范数,因此 *x > 0* (请参见下图): + +![](img/9b0a1cc5-3e19-4f90-9f9d-70dc2ecafe72.png) + +*W ij* 是每个初级胶囊中 *u i* ,i∈(1、32 x 6 x 6)和 *v j* ,在 DigitCaps 中 j∈(1,10)。 在此, **![](img/f08f4ab8-7dca-4fe4-ae07-5d159b65881b.png)** 被称为预测向量,并且像一个已转换(旋转/翻译)的输入胶囊向量 *u i * 。 胶囊的总输入 *s j * 是来自下一层胶囊的所有预测向量的加权总和。 这些权重 *c ij * 的总和为 1,在 Hinton 中称为**耦合系数**。 一开始,它假设胶囊 *i* 应该与母体胶囊 *j* 结合的对数先验概率对于所有 *i* 和 *j 都是相同的* ,用 *b ij * 表示。 因此,可以通过此众所周知的 softmax 转换来计算耦合系数: + +![](img/0890948e-245c-4806-90f2-b79e2218dbbe.png) + +通过称为**协议路由**的算法,这些耦合系数与网络的权重一起迭代更新。 简而言之,它执行以下操作:如果主胶囊 *i* 的预测矢量与可能的父级 *j* 的输出具有大的标量积,则耦合系数 *b* *i* j 对于该父对象增加而对于其他父对象减少。 + +完整的路由算法在这里给出: + +![](img/5dda55cb-a1a3-4ce4-a112-ec0d16c5466f.png) + +左侧显示了如何通过权重矩阵 *W ij* 将所有主胶囊连接到数字胶囊。 此外,它还描述了如何通过非线性压缩函数计算耦合系数以及如何计算 DigitCaps 的十六维输出。 在右侧,假设主胶囊捕获了两个基本形状:输入图像中的三角形和矩形。 旋转后将它们对齐会根据旋转量给出**房屋**或**帆船**。 很明显,这两个物体在极少或几乎没有旋转的情况下结合在一起,形成了帆船。 即,两个主胶囊比房屋更对准以形成船。 因此,路由算法应更新 b i, 的耦合系数: + +```py +procedure routing (, r, l): + for all capsule i in layer l and capsule j in layer (l + 1): bij <- 0 +for r iterations do: + for all capsule i in layer l: ci <- softmax (bi) + for all capsule j in layer (l + 1): + for all capsule j in layer (l + 1): vj <- squash (sj) + for all capsule i in layer l and capsule j in layer (l + 1): +return vj +``` + +最后,我们需要适当的损失函数来训练该网络。 在此,将*数字存在的余量损失*用作损失函数。 它还考虑了数字重叠的情况。 对于每个手指囊 *k* ,单独的裕量损失 L k 用于允许检测多个重叠的数字。 L k 观察胶囊矢量的长度,对于 *k* 类的数字,第 k 胶囊矢量的长度应最大。 : + +![](img/eca8cda7-e58c-4af2-ad66-5d3f9208be75.png) + +如果存在第 k 个第位,则 T k = 1。 m + = 0.9,m - = 0.1。 λ用于降低缺少数字类别的损失的权重。 与 L k s 一起,图像重建误差损失被用作网络的正则化。 如 CapsNet 架构所示,数字胶囊的输出被馈送到由三个完全连接的层组成的解码器中。 逻辑单元的输出与原始图像像素强度之间的平方差之和最小。 重建损失缩小了 0.0005 倍,因此在训练过程中它不控制边际损失。 + +CapsNet 的 TensorFlow 实现在此处可用: [https://github.com/naturomics/CapsNet-Tensorflow](https://github.com/naturomics/CapsNet-Tensorflow) 。 + +# 递归神经网络 + +**递归神经网络**( **RNN** )专用于处理一系列值,如 x(1)中的。 。 。 ,x(t)。 例如,如果要在给定序列的最新历史的情况下预测序列中的下一个术语,或者将一种语言的单词序列翻译为另一种语言,则需要进行序列建模。 RNN 与前馈网络的区别在于其体系结构中存在反馈环路。 人们常说 RNN 有记忆。 顺序信息保留在 RNN 的隐藏状态中。 因此,RNN 中的隐藏层是网络的内存。 从理论上讲,RNN 可以任意长的顺序使用信息,但实际上,它们仅限于回顾一些步骤。 + +我们将在后面解释: + +![](img/3fe6be82-124d-4f5d-9594-db0a4b4c22af.png) + +通过展开网络中的反馈回路,我们可以获得前馈网络。 例如,如果我们的输入序列的长度为 4,则可以按以下方式展开网络。 展开相同的权重集后,在所有步骤中共享 *U* , *V* 和 *W* ,与传统 DNN 不同,传统 DNN 在 每层。 因此,实际上我们在每个步骤都执行相同的任务,只是输入的内容不同。 这大大减少了我们需要学习的参数总数。 现在,要学习这些共享的权重,我们需要一个损失函数。 在每个时间步长处,我们都可以将网络输出 *y(t)*与目标序列 *s(t)*进行比较,并得出误差 *E(t )*。 因此,总错误为![](img/8d0d1fbc-ce0d-4beb-93f9-259f72be5b1f.png): + +![](img/74d14517-90ed-4828-aad4-1cb89b7b96e1.png) + +让我们看一下使用基于梯度的优化算法学习权重所需的总误差导数。 我们有![](img/bad934bc-2baf-4446-8bc6-e356e6bc6049.png),Φ是非线性激活,而![](img/6e1f5245-8381-41b4-8de3-f0ad95f59569.png)。 + +现在是![](img/c591f7f1-05aa-4bda-8f15-b98ea25a8336.png),根据链式规则,我们有![](img/1b5b0979-6df5-4b37-ba08-82f68f8c5558.png)。 + +在此,雅可比行列式![](img/d16b854f-b4e3-447f-8ff4-6702a6e5743e.png),即层 *t* 相对于前一层 *k-* 本身是雅可比行列式![](img/2abf6ed9-391a-4e77-a7d4-5b5757349a19.png)的乘积。 + +使用前面的 h t 方程,我们有![](img/eda57a9e-cf19-4646-bd30-9138f3371662.png)。 因此,雅可比式![](img/8c73dc5a-8829-42f1-8c3b-2f3e29daf28e.png)的范数由乘积![](img/044603aa-f36f-4c54-b019-71057b3fb0b1.png)给出。 如果数量![](img/04ef4106-bd1b-484b-a744-92388cbf2bcd.png)小于 1,则在较长的序列(例如 100 步)中,这些规范的乘积将趋于零。 同样,如果范数大于 1,则长序列的乘积将成倍增长。 这些问题在 RNN 中称为**消失梯度**和**爆炸梯度**。 因此,在实践中,RNN 不能具有很长的记忆。 + +# LSTM + +随着时间的流逝,RNN 开始逐渐失去历史背景,因此很难进行实际训练。 这就是 LSTM 出现的地方! LSTM 由 Hochreiter 和 Schmidhuber 于 1997 年引入,可以记住来自非常长的基于序列的数据中的信息,并可以防止梯度消失等问题。 LSTM 通常由三个或四个门组成,包括输入,输出和忘记门。 + +下图显示了单个 LSTM 单元的高级表示: + +![](img/28d6b1a5-598b-4a4b-8941-33a4ee4718dd.png) + +输入门通常可以允许或拒绝进入的信号或输入以更改存储单元状态。 输出门通常会根据需要将该值传播到其他神经元。 遗忘门控制存储单元的自循环连接以根据需要记住或忘记以前的状态。 通常,将多个 LSTM 单元堆叠在任何深度学习网络中,以解决现实世界中的问题,例如序列预测。 在下图中,我们比较了 RNN 和 LSTM 的基本结构: + +![](img/c2f733e7-5563-4092-aeec-2e286cba3f6d.png) + +下图显示了 LSTM 单元和信息流的详细架构。 令 *t* 表示一个时间步长; *C* ,细胞状态; *h* 为隐藏状态。 通过称为*门*的结构,LSTM 单元具有删除信息或向单元状态添加信息的能力。 门 *i* , *f* 和 *o* 分别代表输入门,忘记门和输出门,它们中的每一个都由 S 形层调制, 输出从零到一的数字,控制这些门的输出应通过多少。 因此,这有助于保护和控制单元状态: + +![](img/572ea417-a601-4d49-b75c-24e4f2f6a6e0.png) + +通过 LSTM 的信息流包括四个步骤: + +1. **决定要从单元状态中丢弃哪些信息**:该决定由称为**遗忘门层**的 S 形决定。 将仿射变换应用于 h t ,x t-1 ,并将其输出通过 S 型挤压函数传递,以得到一个介于 0 和 1 之间的数字。 细胞状态 C t-1 。 1 表示应保留内存,零表示应完全擦除内存。 + +2. **决定将哪些新信息写入内存**:这是一个两步过程。 首先,使用一个称为**输入门层**的 S 形层,即 t 来确定将信息写入哪个位置。接下来,tanh 层将创建新的候选信息。 书面。 +3. **更新内存状态**:将旧的内存状态乘以 *f t* ,擦除确定为可忘记的内容。 然后,在通过 *i* *t* 缩放它们之后,添加在*步骤 2* 中计算的新状态信息。 +4. **输出存储器状态**:单元状态的最终输出取决于当前输入和更新的单元开始。 首先,使用 S 形层来确定我们要输出的单元状态的哪些部分。 然后,单元状态通过 tanh 并乘以 S 型门的输出。 + +我建议您在 [http://colah.github.io/posts/2015-08-Understanding-LSTMs/](http://colah.github.io/posts/2015-08-Understanding-LSTMs/) 上查看 Christophers 的博客,以获取有关 LSTM 步骤的更详细说明。 我们在这里查看的大多数图表均来自此。 + +LSTM 可以用于序列预测以及序列分类。 例如,我们可以预测未来的股价。 另外,我们可以使用 LSTM 构建分类器,以预测来自某些健康监控系统的输入信号是致命还是非致命信号(二进制分类器)。 我们甚至可以使用 LSTM 构建文本文档分类器。 单词序列将作为 LSTM 层的输入,LSTM 的隐藏状态将连接到密集的 softmax 层作为分类器。 + +# 堆叠式 LSTM + +如果我们想了解顺序数据的分层表示,可以使用 LSTM 层的堆栈。 每个 LSTM 层输出一个向量序列,而不是序列中每个项的单个向量,这些向量将用作后续 LSTM 层的输入。 隐藏层的这种层次结构使我们的顺序数据可以更复杂地表示。 堆叠的 LSTM 模型可用于对复杂的多元时间序列数据进行建模。 + +# 编码器-解码器–神经机器翻译 + +机器翻译是计算语言学的一个子领域,涉及将文本或语音从一种语言翻译成另一种语言。 传统的机器翻译系统通常依赖于基于文本统计属性的复杂功能工程。 最近,深度学习已被用于解决此问题,其方法称为**神经机器翻译**( **NMT** )。 NMT 系统通常由两个模块组成:编码器和解码器。 + +它首先使用*编码器*读取源句子,以构建思想向量:代表该句子含义的数字序列。 *解码器*处理句子向量以发出对其他目标语言的翻译。 这称为编码器-解码器体系结构。 编码器和解码器通常是 RNN 的形式。 下图显示了使用堆叠 LSTM 的编码器-解码器体系结构。 在这里,第一层是一个嵌入层,用于通过密集的实向量来表示源语言中的单词。 为源语言和目标语言都预定义了词汇表。 不在词汇表中的单词由固定单词 *<未知>* 表示,并由固定嵌入向量表示。 该网络的输入首先是源句子,然后是句子标记 *< s >* 的结尾,指示从编码模式到解码模式的转换,然后目标句子是 馈入: + +![](img/9c3d34f8-aa07-489d-b95e-1d215daf0dee.png) + +资料来源:https://www.tensorflow.org/tutorials/seq2seq + +输入嵌入层后面是两个堆叠的 LSTM 层。 然后,投影层将最上面的隐藏状态转换为尺寸为 **V** (目标语言的词汇量)的 logit 向量。 这里,交叉熵损失用于通过反向传播训练网络。 我们看到在训练模式下,源句子和目标句子都被输入到网络中。 在推理模式下,我们只有源句。 在那种情况下,可以通过几种方法来完成解码,例如贪婪解码,与贪婪解码结合的注意力机制以及波束搜索解码。 我们将在这里介绍前两种方法: + +![](img/52fe1877-dded-4161-8520-ba0d425f2452.png) + +在贪婪的解码方案中(请参见前面两个图的左手图),我们选择最有可能的单词(以最大 logit 值描述为发射的单词),然后将其反馈给解码器作为输入。 继续该解码过程,直到产生句子结束标记 *< / s >* 作为输出符号。 + +由源句子的句子结尾标记生成的上下文向量必须对我们需要了解的有关源句子的所有内容进行编码。 它必须充分体现其含义。 对于长句子,这意味着我们需要存储非常长的记忆。 研究人员发现,反转源序列或两次馈入源序列有助于网络更好地记忆事物。 对于与英语非常相似的法语和德语这样的语言,反转输入是有意义的。 对于日语,句子的最后一个单词可能会高度预测英语翻译中的第一个单词。 因此,这里的反转会降低翻译质量。 因此,一种替代解决方案是使用**注意机制**(如前两个图的右图所示)。 + +现在,无需尝试将完整的源句子编码为固定长度的向量,而是允许解码器在输出生成的每个步骤中使*参与*到源句子的不同部分。 因此,我们将第 t 个,第个目标语言单词的基于注意力的上下文向量 c t 表示为所有先前源隐藏状态:![](img/823d1b46-4dad-4194-b79e-9cef6d5164fa.png)的加权和。 注意权重为![](img/d2be8a4c-1bd0-415e-ae4f-39d41a0985df.png),*得分*的计算如下:*得分(h t ,h s )= h t Wh s * 。 + +*W* 是权重矩阵,将与 RNN 权重一起学习。 该得分函数称为 **Luong 的**乘法样式得分。 此分数还有其他一些变体。 最后,通过将上下文向量与当前目标隐藏状态组合如下来计算**注意向量**, **a t** :![](img/4e505ad7-0de3-474e-b874-95c380aec9b5.png)。 + +注意机制就像只读存储器,其中存储了源的所有先前隐藏状态,然后在解码时读取它们。 TensorFlow 中 NMT 的源代码可在此处获得: [https://github.com/tensorflow/nmt/](https://github.com/tensorflow/nmt/) 。 + +# 门控循环单元 + +**门控循环单元**( **GRU** )与 LSTM 相关,因为两者均利用不同的门控信息方式来防止梯度消失和存储长期记忆。 GRU 具有两个门:重置门 *r* 和更新门 *z* ,如下图所示。 复位门确定如何将新输入与先前的隐藏状态 h t-1 组合在一起,而更新门则确定要保留多少先前状态信息。 如果我们将重置设置为全 1 并将更新门更新为全零,我们将得到一个简单的 RNN 模型: + +![](img/35710e6e-1f70-4ab8-9727-9c318f0e0d4f.png) + +GRU 相对较新,其性能与 LSTM 相当,但由于结构更简单,参数更少,因此它们在计算上更加高效。 这是 LSTM 和 GRU 之间的一些结构差异: + +* 一个 GRU 有两个门,而 LSTM 有三个门。 GRU 没有 LSTM 中存在的输出门。 +* 除隐藏状态外,GRU 没有其他内部内存 C t 。 +* 在 GRU 中,当计算输出时,非线性(tanh)是*而不是*。 + +如果有足够的数据,建议使用 LSTM,因为 LSTM 的更高表达能力可能会导致更好的结果。 + +# 记忆神经网络 + +大多数机器学习模型无法读取和写入长期内存组件,也无法将旧内存与推理无缝结合。 RNN 及其变体(例如 LSTM)确实具有存储组件。 但是,它们的内存(由隐藏状态和权重编码)通常太小,不像我们在现代计算机中发现的大块数组(以 RAM 的形式)。 他们试图将所有过去的知识压缩为一个密集的向量-记忆状态。 对于诸如虚拟协助或**问题解答**( **QA** )系统之类的复杂应用程序,该应用程序可能会受到很大限制,在这种系统中,长期记忆有效地充当了(动态)知识库, 输出是文本响应。 为了解决这个问题,Facebook AI 研究小组开发了**记忆神经网络**( **MemNNs** )。 MemNN 的中心思想是将深度学习文献中为推理而开发的成功学习策略与可以像 RAM 一样读写的内存组件相结合。 同样,模型被训练以学习如何与存储组件一起有效地操作。 存储网络由存储器 **m** ,对象的索引数组(例如,向量或字符串数​​组)和要学习的四个组件 **I,G,O** 组成。 和 **R** : + +* **I** :输入要素映射 **I** ,它将输入输入转换为内部要素表示。 +* **G** :*通用化*组件, **G** ,在输入新输入的情况下更新旧内存。 这被称为**泛化**,因为网络有机会在此阶段压缩和泛化其内存,以备将来使用。 +* **O** :输出特征图 **O** ,在给定新输入和*当前存储状态*的情况下,该特征图空间中将生成新输出。 +* **R** :*响应*组件 **R** ,可将输出转换为所需的响应格式,例如文本响应或动作: + +![](img/18baa50d-ad2a-47cf-8b6f-59d223cc0ea7.png) + +当分量 I,G,O 和 R 是*神经网络*时,则所得系统称为 **MemNN** 。 让我们尝试通过示例质量检查系统来理解这一点。 系统将获得一系列事实和问题。 它将输出该问题的答案。 我们有以下六个文本事实和一个问题,问:*现在的牛奶在哪里?* : + +* 乔去了厨房 +* 弗雷德去了厨房 +* 乔拿起牛奶 +* 乔去了办公室 +* 乔离开了牛奶 +* 乔去洗手间 + +请注意,语句的某些子集包含答案所需的信息,而其他子集本质上是无关紧要的。 我们将用 MemNN 模块 I,G,O 和 R 来表示这一点。模块 **I** 是一个简单的嵌入模块,它将文本转换为二进制词袋向量。 文本以其原始形式存储在下一个可用的存储插槽中,因此 **G** 模块非常简单。 一旦去除了停用词,给定事实中使用的单词词汇为 V = {乔,弗雷德,旅行,捡起,离开,离开,去办公室,洗手间,厨房,牛奶}。 现在,这是所有文本存储后的内存状态: + +| **内存插槽号** | 乔 | 弗雷德 | ... | 办公室 | 浴室 | 厨房 | 牛奶 | +| 1 | 1 | 0 | | 0 | 0 | 1 | 0 | +| 2 | 0 | 1 | | 0 | 0 | 1 | 0 | +| 3 | 1 | 0 | | 0 | 0 | 0 | 1 | +| 4 | 1 | 0 | | 1 | 0 | 0 | 0 | +| 5 | 1 | 0 | | 0 | 0 | 0 | 1 | +| 6 | 1 | 0 | | 0 | 1 | 0 | 0 | +| 7 | | | | | | | | + +**O** 模块通过在给定问题 q 的情况下找到 k 个支持存储器来产生输出特征。 对于 k = 2,使用以下方法检索最高得分的支持内存: + +![](img/113b62ab-656d-4d22-9e1c-26c2855cf9e5.png) + +其中 *s 0* 是对输入 *q* 和 *m* *i [* 。 o1 是具有最佳匹配的内存索引 *m* 。 现在,使用查询和第一个检索到的内存,我们可以检索下一个内存 m o2 ,这两个内存都很接近: + +![](img/2b447e60-be65-4f11-a9e0-0b28d3c0e730.png) + +合并的查询和内存结果为 o = [q,m o1 ,m o2 ] = [现在牛奶在哪里,乔离开了牛奶。乔去了办公室。 ]。 最后,模块 **R** 需要产生文本响应 *r* 。 *R* 模块可以输出一个单词的答案,或者可以输出一个完整句子的 RNN 模块。 对于单字响应,令 r 是对[q,m o1 ,m o2 ]和单词 *w* 。 因此,最后的回应 *r* 是办公室一词: + +![](img/7edbfaa7-ef11-4918-b0bf-23ecb6bb5098.png) + +这种模型很难使用反向传播来进行端到端训练,并且需要在网络的每个模块上进行监督。 对此有一点修改,实际上是称为**端到端存储网络**( **MemN2N** )的连续版本的存储网络。 该网络可以通过反向传播进行训练。 + +# MemN2Ns + +我们从一个查询开始:牛奶现在在哪里? 使用大小为 *V* 的向量,用成袋的单词进行编码。 在最简单的情况下,我们使用嵌入 *B(d x V)*将向量转换为大小为 *d* 的词嵌入。 我们有 **u = embeddingB(q)**: + +![](img/d6252a46-1170-4bd3-a61c-ec067acd5310.png) + +输入句子 x1,x2,...和 xi 通过使用另一个嵌入矩阵 A(dx Vd x V)存储在内存中,其大小与 B **mi = embeddingA(x i [** **)**。 每个嵌入式查询 *u* 与每个内存 *m i* 之间的相似度是通过取内积和 softmax 来计算的: **p i = softmax(u T m i )**。 + +输出存储器表示如下:每个 xi 具有对应的输出向量 *c i* ,可以用另一个嵌入矩阵 *C* 表示。 然后,来自存储器的响应向量 *o* 是 *c i* 上的总和,并由来自以下输入的概率向量加权: + +**![](img/f448c97e-6ab7-4718-90ff-cc360122bce3.png)** + +最后,将 *o* 和 *u* 之和与权重矩阵 W(V x d)相乘。 结果传递到 softmax 函数以预测最终答案: + +![](img/00a99297-3d3b-4387-a888-ff58f137672d.png) + +MemN2N 的 TensorFlow 实现可在此处获得: [https://github.com/carpedm20/MemN2N-tensorflow](https://github.com/carpedm20/MemN2N-tensorflow) 。 + +# 神经图灵机 + +**神经图灵机**( **NTM** )受到**图灵机**( **TM** )的启发:定义了一个 抽象机。 TM 可以根据规则表来操作一条胶带上的符号。 对于任何计算机算法,TM 都可以模拟该算法的逻辑。 机器将其头放在单元格上方并在其中读取或写入符号。 此后,根据定义的规则,它可以向左或向右移动甚至停止程序。 + +NTM 体系结构包含两个基本组件:神经网络控制器和内存。 下图显示了 NTM 体系结构的高层表示: + +![](img/08a67c09-f33c-4ff9-aa7c-0d8d43e74d6d.png) + +控制器使用输入和输出向量与外部世界进行交互。 与标准神经网络不同,此处的控制器还使用选择性读取和写入操作与存储矩阵进行交互。 内存是一个实值矩阵。 内存交互是端到端可区分的,因此可以使用梯度下降对其进行优化。 NTM 可以从输入和输出示例中学习简单的算法,例如复制,排序和关联召回。 而且,与 TM 不同,NTM 是可通过梯度下降训练的可微分计算机,为学习程序提供了一种实用的机制。 + +控制器可以由 LSTM 建模,LSTM 具有自己的内部存储器,可以补充矩阵中更大的存储器。 可以将控制器与计算机中的 CPU 相比较,并且可以将存储矩阵与计算机的 RAM 相比较。 + +读写头选择要读取或写入的内存部分。 可以通过神经网络中的隐藏层(可能是 softmax 层)对它们进行建模,以便可以将它们视为外部存储单元上的权重之和,这些权重之和为 1。此外,请注意,模型参数的数量是受控的 不会随存储容量的增长而增加。 + +# 选择性注意 + +控制器输出用于确定要读取或写入的存储器位置。 这由一组分布在所有内存位置上的权重定义,这些权重之和为 1。权重由以下两种机制定义。 想法是为控制器提供几种不同的读取或写入内存的模式,分别对应于不同的数据结构: + +* **基于内容的**:使用相似度度量(例如余弦相似度(S))将控制器的键 **k** 输出与所有存储位置进行比较,然后所有距离均由 softmax 归一化 得到的权重加起来为 1: + +![](img/9c2898fa-79f4-4806-a527-d2a1fbf1f977.png) + +在这种情况下,β≥1 称为清晰度参数,并控制对特定位置的聚焦。 它还为网络提供了一种方法来决定其希望内存位置访问的精确度。 就像模糊 c 均值聚类中的模糊系数。 + +* **基于位置的**:基于位置的寻址机制旨在跨存储器位置的简单迭代。 例如,如果当前权重完全集中在单个位置上,则旋转 1 会将焦点移到下一个位置。 负移将使权重朝相反方向移动。 控制器输出一个移位内核 *s* (即[-n,n]上的 softmax),将其与先前计算的存储器权重进行卷积以产生移位的存储器位置,如下图所示。 这种转变是循环的; 也就是说,它环绕边界。 下图是内存的热图表示—较深的阴影表示更多的权重: + +![](img/0952f060-fd33-4b79-a530-a3ef4c96a006.png) + +在应用旋转移位之前,将内容寻址所给定的权重向量与先前的权重向量 w t-1 相结合,如下所示:![](img/31390bf5-05d0-4451-926c-5146b31a240c.png)。 在此,g t 是由控制器头发出的标量*内插门*,范围为(0,1)。 如果 g t = 1,则忽略先前迭代的加权。 + +# 读取操作 + +令 **M t** 为时间 *t* 时 N x M 存储矩阵的内容,其中 *N* 是存储位置的数量, *M* 是每个位置的向量大小。 时间 *t* 的读取头由向量 **w t** ,给出。 + +![](img/1e4d609a-d3bb-41ee-8b63-6e137fa2eb4a.png) + +*M* 读取向量 **r t** 的长度定义为行向量 **M t (i)的凸组合[** 在内存中: + +![](img/3e35f31d-36ad-4097-aa25-7dcd5b0fe6ad.png) + +# 写操作 + +每个写头接收一个*擦除向量*, **e t** 和一个*加*向量,**a t** ,以像 LSTM 单元一样重置和写入存储器,如下所示:M t (i)←M t (i) [1- e t (i) w t (i)] + w t (i)a t (i)。 + +这是上述操作的伪代码: + +```py +mem_size = 128 #The size of memory +mem_dim = 16 #The dimensionality for memory +shift_range = 1 # defining shift[-1, 0, 1] + +## last output layer from LSTM controller: last_output +## Previous memory state: M_prev +def Linear(input_, output_size, stddev=0.5): + '''Applies a linear transformation to the input data: input_ + implements dense layer with tf.random_normal_initializer(stddev=stddev) + as weight initializer + '''' +def get_controller_head(M_prev, last_output, is_read=True): + + k = tf.tanh(Linear(last_output, mem_dim)) + # Interpolation gate + g = tf.sigmoid(Linear(last_output, 1) + + # shift weighting + w = Linear(last_output, 2 * shift_range + 1) + s_w = softmax(w) + + # Cosine similarity + similarity = smooth_cosine_similarity(M_prev, k) # [mem_size x 1] + # Focusing by content + content_focused_w = softmax(scalar_mul(similarity, beta)) + + # Convolutional shifts + conv_w = circular_convolution(gated_w, s_w) + + if is_read: + read = matmul(tf.transpose(M_prev), w) + return w, read + else: + erase = tf.sigmoid(Linear(last_output, mem_dim) + add = tf.tanh(Linear(last_output, mem_dim)) + return w, add, erase +``` + +NTM 的完整 TensorFlow 实现可在此处获得: [https://github.com/carpedm20/NTM-tensorflow](https://github.com/carpedm20/NTM-tensorflow) 。 NTM 算法可以学习复制-他们可以学习复制随机数序列的算法。 下面显示了 NTM 如何使用内存读写头并将其移位以实现复制算法: + +![](img/0284727e-ebf8-4bc9-8074-24022d4a2558.png) + +类似地,给定一组随机序列和相应的排序序列,NTM 可以从数据中高效地学习排序算法。 + +# 基于注意力的神经网络模型 + +我们已经讨论了基于注意力的机器翻译模型。 基于注意力的模型的优点在于,它们提供了一种解释模型并理解其工作方式的方式。 注意机制是记忆以前的内部状态的一种形式。 这就像内部存储器。 与典型的存储器不同,这里的存储器访问机制是软的,这意味着网络将检索所有存储器位置的加权组合,而不是单个离散位置的值。 软存储器访问使通过反向传播训练网络变得可行。 基于注意的体系结构不仅用于机器翻译,还可以用于自动生成图像标题。 + +这项工作于 2016 年发表在论文 *Show,Attend and Tell:带有视觉注意的神经图像字幕生成*上,作者是 Kelvin Xu 及其合著者( [https://arxiv.org/abs /1502.03044](https://arxiv.org/abs/1502.03044) )。 在这里,从注意力权重来看,我们看到随着模型生成每个单词,其注意力发生变化以反映图像的相关部分。 此关注模型的 TensorFlow 实现可在此处获得 [https://github.com/yunjey/show-attend-and-tell](https://github.com/yunjey/show-attend-and-tell) : + +![](img/9c470dba-210a-4bfd-8a95-1227d45362b9.png) + +# 摘要 + +本章介绍了神经网络体系结构的各种进展及其在各种实际问题中的应用。 我们讨论了对这些体系结构的需求,以及为什么简单的深度多层神经网络不能充分解决各种问题,因为它具有强大的表达能力和丰富的假设空间。 讨论转移学习用例时,将在后面的章节中使用其中讨论的许多体系结构。 提供了几乎所有体系结构的 Python 代码参考。 我们还试图清楚地解释一些最近的体系结构,例如 CapsNet,MemNN 和 NTM。 当您逐步学习迁移学习用例时,我们将经常参考本章。 + +下一章将介绍转学的概念。 \ No newline at end of file diff --git a/docs/handson-tl-py/4.md b/docs/handson-tl-py/4.md new file mode 100644 index 0000000000000000000000000000000000000000..d0a1e94916200c5b3ffe0acf0cf24b5bcbc71e11 --- /dev/null +++ b/docs/handson-tl-py/4.md @@ -0,0 +1,197 @@ +# 转移学习基础 + +我还在学习 + +–米开朗基罗 + +人类具有在任务之间传递知识的固有能力。 我们在学习一项任务时获得的知识就是我们以相同的方式来解决相关任务。 任务越相关,我们就越容易转移或交叉利用知识。 到目前为止,到目前为止,机器学习和深度学习算法都是设计为独立工作的。 这些算法经过训练可以解决特定任务。 一旦特征空间分布发生变化,就必须从头开始重建模型。 转移学习是克服孤立的学习范式,并利用一项任务获得的知识来解决相关任务的想法。 在本章中,我们将介绍转移学习的概念,并专注于深度学习上下文的各个方面。 本章将涵盖以下主题: + +* 转移学习导论 +* 转移学习策略 +* 通过深度学习传递知识 +* 深度迁移学习的类型 +* 转移学习的挑战 + +# 转移学习导论 + +传统上,学习算法设计为单独解决任务或问题。 根据用例和手头数据的要求,应用算法来训练给定特定任务的模型。 传统的**机器学习**( **ML** )根据特定的域,数据和任务单独地训练每个模型,如下图所示: + +![](img/0c813b73-4ddd-42c6-a8c1-79fc1597e8f2.png) + +传统机器学习 + +转移学习使学习的过程更进一步,并且更加符合人类如何跨任务利用知识。 因此,转移学习是一种将模型或知识重用于另一个相关任务的方法。 迁移学习有时也被视为现有 ML 算法的扩展。 在转移学习的背景下,以及在了解如何在任务之间转移知识的过程中,正在进行大量的研究和工作。 但是,**神经信息处理系统**( **NIPS** )1995 研讨会*学习:归纳系统中的知识整合和转移*被认为为 在这个领域的研究。 + +NIPS 1995 的所有研讨会都在此处列出: [http://www.cs.cmu.edu/afs/cs/project/cnbc/nips/NIPS95/Workshops.html](http://www.cs.cmu.edu/afs/cs/project/cnbc/nips/NIPS95/Workshops.html) 。 + +从那时起,诸如*学习学习*,*知识整合*和*归纳转移*等术语已与转移学习互换使用。 总是有不同的研究人员和学术著作提供不同背景下的定义。 在他们的书*深度学习*中,Goodfellow 等人。 在泛化的背景下指转移学习。 它们的定义如下: + +*利用一种情况下所学的知识来改善另一种情况下的泛化的情况。* + +让我们借助示例来了解前面的定义。 假设我们的任务是在餐厅的受限区域内识别图像中的对象。 让我们在定义的范围内将此任务标记为 T 1 。 给定该任务的数据集,我们训练模型并对其进行调整,以使它在来自同一域(餐厅)的看不见的数据点上表现良好(概括)。 当我们在给定领域中没有足够的训练示例来完成所需的任务时,传统的监督 ML 算法就会崩溃。 假设我们现在必须从公园或咖啡馆中的图像中检测物体(例如,任务 T 2 )。 理想情况下,我们应该能够应用针对 T 1 训练的模型,但实际上,我们面临着性能下降和模型不能很好推广的问题。 发生这种情况的原因多种多样,我们可以广泛地和集体地将其称为模型对训练数据和领域的偏见。 因此,转移学习使我们能够利用先前学习的任务中的知识,并将其应用于更新的相关任务。 如果我们拥有任务 T 1 的大量数据,则可以利用其学习并将其概括用于任务 T 2 (其数据要少得多)。 在图像分类的情况下,某些低级特征(例如边缘,形状和照明)可以在任务之间共享,因此可以在任务之间传递知识。 + +下图显示了转移学习如何使现有知识重用于新的相关任务: + +![](img/8a932ea8-9a32-4475-8017-88d5e3fb917b.png) + +如上图所示,在学习目标任务时,来自现有任务的知识将作为附加输入。 + +# 转移学习的优势 + +我们利用源模型中的知识来改进目标任务中的学习。 除了提供重用已经构建的模型的功能之外,转移学习还可以通过以下方式帮助学习目标任务: + +* **改进了基准性能**:当我们使用源模型中的知识来增强孤立的学习者(也称为**无知学习者**)的知识时,由于这种知识转移,基准性能可能会提高 。 +* **模型开发时间**:与从头开始学习的目标模型相比,利用源模型的知识也可能有助于充分学习目标任务。 反过来,这会导致开发/学习模型所需的总时间减少。 +* **改进的最终性能**:可以利用转移学习获得更高的最终性能。 + +读者应注意,有可能获得这些收益中的一项或多项,我们将在接下来的章节中详细讨论。 如下图所示,它显示出更好的基线性能(**更高的起始**),效率增益(**更高的斜率**)和更好的最终性能(**渐近线**更高) : + +![](img/8f69a68a-52a7-4ae5-a2b1-019f7a344fc4.png) + +使用转移学习的可能好处(来源:转移学习,Lisa Torrey 和 Jude Shavlik) + +转移学习已在归纳学习者(例如神经网络和贝叶斯网络)的上下文中得到应用和研究。 强化学习是另一个探索转移学习可能性的领域。 因此,转移学习的概念不限于深度学习。 + +在本章及后续章节中,我们将限制使用转移学习的范围仅限于深度学习的上下文。 + +# 转移学习策略 + +首先让我们看一下迁移学习的正式定义,然后利用它来理解不同的策略。 在他们的论文*迁移学习调查*( [https://www.cse.ust.hk/~qyang/Docs/2009/tkde_transfer_learning.pdf](https://www.cse.ust.hk/~qyang/Docs/2009/tkde_transfer_learning.pdf) )中,潘和杨使用 域,任务和边际概率,以提供用于理解迁移学习的框架。 该框架定义如下: + +域 *D* 定义为由特征空间![](img/bc86d76b-a52c-4244-bcfb-fa61a5a35639.png)和边际概率 *P(Χ)*组成的二元元组,其中*Χ*是样本数据点。 + +在此, *x = {x 1 ,x 2 .... x n }* ,其中 *x i 作为特定载体的* 和 X ![](img/4dddc3e3-867d-4cae-b6aa-385758c54f6c.png) ![](img/4bcb6159-c17e-40de-9a72-0136cecd72f3.png)。 从而: + +![](img/bedd445a-346d-4dd4-97f8-bf38e2fe755c.png) + +另一方面,可以将任务 *T* 定义为标签空间γ和目标函数 *f* 的二元组。 从概率的观点来看,目标函数也可以表示为 *P(γ|Χ)*。 从而: + +![](img/d21e706b-b7fc-4321-8536-06c2f01ee8ce.png) + +使用此框架,我们可以将迁移学习定义为旨在改善目标目标功能 *f T* (或目标任务, *T T )的过程。* ),使用来自 *T* *S* 源的知识,在目标域中将 *D T* *D S* 域中的任务。 这导致以下四种情况: + +* **特征空间**:源域和目标域的特征空间互不相同,例如χ s ≠χ t 。 例如,如果我们的任务与文档分类有关,则此方案以不同的语言引用源任务和目标任务。 +* **边际概率**:边际概率或源域和目标域不同,例如 P(X s )≠P(X t )。 这种情况也称为**域适配** *。* +* **标签空间**:在这种情况下,源域和目标域的标签空间不同,例如γ s ≠γ t 。 这通常也意味着方案 4 的存在-不同的条件概率。 +* **条件概率**:在这种情况下, *P(Υ ss )≠P(Υ tt )*,因此源域和目标域中的条件概率不同。 + +到目前为止,我们已经看到转移学习具有在目标任务中利用来自源学习者的现有知识的能力。 在迁移学习过程中,必须回答以下三个重要问题: + +* **传输什么**:这是整个过程中的第一步,也是最重要的一步。 我们尝试寻求有关知识的哪一部分可以从源转移到目标的答案,以提高目标任务的性能。 当试图回答这个问题时,我们试图确定知识的哪一部分是特定于来源的,以及哪些是来源与目标之间的共同点。 +* **何时转让**:在某些情况下,为了知识而转让知识可能比改善任何事情都变得更糟(也称为*负向转让*)。 我们应该以利用转移学习为目标,以提高目标任务的绩效/结果,而不是使其退化。 我们需要注意何时转移以及何时不转移。 +* **如何转移**:一旦回答了什么和什么时候,我们便可以着手确定跨领域/任务实际转移知识的方式。 这涉及对现有算法和不同技术的更改,我们将在本章的后续部分中介绍。 此外,下一节将列出特定的用例,以更好地了解如何进行传输。 + +Pan 和 Yang 撰写的论文*迁移学习调查*可以在此处找到: [https://www.cse.ust.hk/~qyang/Docs/2009/tkde_transfer_learning.pdf](https://www.cse.ust.hk/~qyang/Docs/2009/tkde_transfer_learning.pdf) 。 + +分组技术可帮助我们了解整体特征并提供更好的框架来利用它们。 可以根据所涉及的传统 ML 算法的类型对转移学习方法进行分类,例如: + +* **归纳传输**:在这种情况下,源域和目标域相同,但是源任务和目标任务彼此不同。 该算法尝试利用源域的归纳偏置来帮助改进目标任务。 根据源域是否包含标记数据,可以将其进一步分为两个子类别,分别类似于*多任务学习*和*自学学习*。 +* **无监督传输**:此设置类似于归纳传输本身,重点是目标域中的无监督任务。 源域和目标域相似,但是任务不同。 在这种情况下,带标签的数据在两个域中都不可用。 +* **传递式传输**:在这种情况下,源任务和目标任务之间存在相似之处,但相应的域不同。 在此设置中,源域具有很多标记数据,而目标域则没有。 可以参考特征空间不同或边际概率不同的设置将其进一步细分为子类别。 + +上一节中讨论的三种转移类别概述了可以应用和详细研究转移学习的不同设置。 为了回答在这些类别中转移什么的问题,可以采用以下一些方法: + +* **实例传输**:从源域到目标任务的知识重用通常是理想的方案。 在大多数情况下,无法直接重用源域数据。 而是,源域中的某些实例可以与目标数据一起重用以改善结果。 在感应传输的情况下,Dai 及其合作者的 *AdaBoost* 之类的修改有助于利用源域中的训练实例来改进目标任务。 +* **特征表示转移**:此方法旨在通过识别可从源域到目标域使用的良好特征表示,以最小化域差异并降低错误率。 根据标记数据的可用性,可以将有监督或无监督的方法应用于基于特征表示的传输。 +* **参数传递**:此方法在以下假设下工作:相关任务的模型共享一些参数或超参数的先前分布。 与多任务学习不同,在多任务学习中同时学习源任务和目标任务,对于转移学习,我们可以对目标域的丢失施加额外的权重以提高整体性能。 +* **关系知识转移**:与前面三种方法不同,关系知识转移尝试处理非 IID 数据,例如非独立且分布相同的数据。 换句话说,每个数据点与其他数据点都有关系的数据; 例如,社交网络数据利用关系知识转移技术。 + +在本节中,我们以非常通用的方式研究了在不同背景和环境下进行迁移学习的不同策略。 现在让我们利用这种理解,学习如何在深度学习的上下文中应用转移学习。 + +# 转移学习和深度学习 + +深度学习模型代表了**归纳学习**。 归纳学习算法的目的是从一组训练示例中得出映射。 例如,在分类的情况下,模型学习输入要素和类标签之间的映射。 为了使这样的学习者能够很好地对看不见的数据进行概括,其算法采用了与训练数据的分布有关的一组假设。 这些假设集合称为**感应偏置**。 + +归纳偏差或假设可以由多个因素来表征,例如它所限制的假设空间以及通过假设空间进行的搜索过程。 因此,这些偏差会影响模型在给定任务和领域上的学习方式和知识。 + +**归纳传输**技术利用源任务的归纳偏差来辅助目标任务。 这可以通过不同的方式来完成,例如通过限制模型空间,缩小假设空间来调整目标任务的归纳偏差,或者借助源任务的知识来对搜索过程本身进行调整。 下图直观地描述了此过程: + +![](img/c65cedd9-a157-4389-8433-18945df2fc9d.png) + +归纳式传递(来源:传递学习,Lisa Torrey 和 Jude Shavlik) + +除了归纳转移,归纳学习算法还利用*贝叶斯*和*层次转移*技术来帮助改进目标任务的学习和性能。 + +# 转移学习方法 + +近年来,深度学习取得了长足的进步,其结果令人赞叹。 但是,这种深度学习系统所需的训练时间和数据量比传统的 ML 系统高出几个数量级。 + +跨计算机视觉和**自然语言处理**([[ **NLP** )。 在大多数情况下,团队/人员共享这些网络的详细信息以供其他人使用([第 3 章](../Text/3.html)和*了解深度学习架构*中共享了一些受欢迎的网络)。 这些预训练的网络/模型在深度学习的背景下构成了转移学习的基础。 + +# 特征提取 + +如[第 3 章](../Text/3.html)和*中所述,了解深度学习体系结构*深度学习系统是分层的体系结构,可在不同的层学习不同的功能。 然后将这些层最终连接到最后一层(在分类的情况下,通常是完全连接的层)以获得最终输出。 这种分层的体系结构使我们可以利用预先训练的网络(例如 Inception V3 或 VGG),而无需将其最终层用作其他任务的固定特征提取器。 下图表示基于特征提取的深度传输: + +![](img/d120bc80-6174-4e45-91a0-7f900fb3142f.png) + +例如,如果我们使用没有最终分类层的 AlexNet,它将帮助我们将新领域任务的图像基于其隐藏状态转换为 4,096 维向量,从而使我们能够利用新领域任务从新领域任务中提取特征 来自源域任务的知识。 这是使用深度神经网络执行转移学习的最广泛使用的方法之一。 + +# 微调 + +这是一项涉及更多的技术,我们不仅要替换最后一层(用于分类/回归),而且还要选择性地重新训练一些先前的层。 深度神经网络是具有各种超参数的高度可配置的体系结构。 如前所述,最初的层已捕获通用特征,而后面的层则更多地关注手头的特定任务。 利用这种洞察力,我们可以在重新训练时冻结(固定权重)某些图层,或者微调其余图层以满足我们的需求。 在这种情况下,我们利用网络整体架构方面的知识,并将其状态用作我们再培训步骤的起点。 反过来,这有助于我们以更少的培训时间获得更好的表现。 + +# 预训练模型 + +迁移学习的基本要求之一是在源任务上表现良好的模型的存在。 幸运的是,深度学习世界相信共享。 他们各自的团队已经公开共享了许多最先进的深度学习架构。 这些跨不同领域,例如计算机视觉和 NLP。 我们在[第 3 章](../Text/3.html)和*了解深度学习架构*中介绍了一些最著名和文档最丰富的架构。 这些网络背后的团队不仅分享了结果,而且分享了他们的预训练模型。 预训练模型通常以数百万个参数/权重的形式共享,该模型在被训练到稳定状态时所达到的模型。 预训练的模型可供每个人通过不同的方式使用。 著名的深度学习 Python 库`keras`提供了下载各种可用的预训练网络的接口,例如 **XCeption** , **VGG16** 和 **InceptionV3** 。 同样,也可以通过 TensorFlow 和其他深度学习库获得预训练的模型。 伯克利的模型动物园( [http://caffe.berkeleyvision.org/model_zoo.html](http://caffe.berkeleyvision.org/model_zoo.html) )提供了多年来开发的更广泛的预训练模型集合。 + +# 应用领域 + +深度学习是一类算法,已被用来非常成功地获得转移学习的好处。 以下是一些示例: + +* **使用文本数据进行转移学习**:当涉及到机器学习和深度学习时,文本数据提出了各种挑战。 这些通常使用不同的技术进行转换或矢量化。 嵌入(例如 Word2vec 和 fastText)已使用不同的训练数据集进行了准备。 通过从源任务中转移知识,这些变量可用于不同任务中,例如情感分析和文档分类。 +* **通过计算机视觉进行转移学习**:深度学习已成功使用各种 CNN 架构成功用于各种计算机视觉任务,例如对象识别。 在他们的论文*中,深层神经网络的特征如何可传递*,Yosinski 及其合作者( [https://arxiv.org/abs/1411.1792](https://arxiv.org/abs/1411.1792) )提出了他们关于 较低的层用作常规的计算机视觉特征提取器,例如边缘检测器,而最后一层则用于特定任务的功能。 因此,这些发现有助于将现有的最新模型,例如 **VGG** , **AlexNet** 和 **Inceptions** 用于目标任务,例如 作为样式转换和面部检测,与这些模型的训练方法不同。 +* **语音/音频转移学习**:类似于文本和计算机视觉领域,深度学习已成功用于基于音频数据的任务。 例如,为英语开发的**自动语音识别**( **ASR** )模型已成功用于提高其他语言(例如德语)的语音识别性能。 同样,自动说话人识别是迁移学习大大帮助的另一个例子。 + +# 深度转移学习类型 + +关于迁移学习的文献经历了许多迭代,并且如本章开头所提到的,与迁移学习相关的术语被松散使用并且经常可以互换使用。 因此,有时难以区分转移学习,领域适应和多任务学习。 放心,这些都是相关的,并尝试解决类似的问题。 为了使本书保持一致,我们将采用转移学习的概念作为一般概念,在此我们将尝试使用源任务域知识来解决目标任务。 + +# 领域适应 + +域适配通常是指源域和目标域之间的边际概率不同的情况,例如 *P(X s )≠P(X t )[* 。 源域和目标域的数据分布存在固有的偏移或漂移,需要进行调整才能转移学习。 例如,标记为肯定或否定的电影评论语料库将不同于产品评论情绪的语料库。 如果用于对产品评论进行分类,则经过电影评论情绪训练的分类器将看到不同的分布。 因此,在这些情况下,领域自适应技术可用于转移学习中。 + +# 域混乱 + +我们学习了不同的转移学习策略,甚至讨论了*,*,*,*和*如何*如何将知识从源转移到目标的三个问题。 特别是,我们讨论了特征表示传递如何有用。 值得重申的是,深度学习网络中的不同层捕获了不同的功能集。 我们可以利用这一事实来学习领域不变的特征,并提高其跨领域的可移植性。 代替让模型学习任何表示,我们将两个域的表示微调为尽可能相似。 + +这可以通过将某些预处理步骤直接应用于表示本身来实现。 孙宝琛,冯家石和 Kate Saenko 在他们的论文中讨论了其中一些问题*令人沮丧的轻松域自适应*( [https://arxiv.org/abs/1511.05547](https://arxiv.org/abs/1511.05547) ) 。 Ganin 等人也提出了对表示相似性的轻推。 等 在他们的论文*中的神经网络领域专家训练*( [https://arxiv.org/abs/1505.07818](https://arxiv.org/abs/1505.07818) )。 该技术背后的基本思想是在源模型中添加另一个目标,以通过混淆域本身来鼓励相似性,从而使*域混淆*。 + +# 多任务学习 + +多任务学习与转移学习世界略有不同。 在多任务学习的情况下,可以同时学习多个任务,而无需区分源和目标。 在这种情况下,与转移学习相比,学习者一次就接收到有关多个任务的信息,在转移学习中,学习者最初对目标任务一无所知。 + +如下图所示: + +![](img/93186224-8b4d-4459-98e7-a21fd5aaf615.png) + +多任务学习:学习者同时从所有任务接收信息 + +# 一键式学习 + +深度学习系统天生就渴望数据,因此它们需要许多训练示例来学习权重。 这是深度神经网络的局限性之一,尽管人类学习并非如此。 例如,一旦向孩子展示了苹果的外观,他们就可以轻松识别出不同种类的苹果(带有一个或几个培训示例); 机器学习和深度学习算法并非如此。 一次学习是转移学习的一种变体,在这种学习中,我们尝试仅根据一个或几个训练示例来推断所需的输出。 这在无法为每个可能的类提供标签数据的现实世界场景中(如果是分类任务)和在经常可以添加新类的场景中非常有用。 + +据说 Fei-Fei 及其合作者具有里程碑意义的论文*对象类别*的一次射击学习 [https://ieeexplore.ieee.org/document/1597116/](https://ieeexplore.ieee.org/document/1597116/) )。 在此子领域创造了“一次性学习和研究”一词。 本文提出了一种用于对象分类的表示学习的贝叶斯框架的变体。 此后,此方法已得到改进,并已使用深度学习系统进行了应用。 + +# 零镜头学习 + +零镜头学习是迁移学习的另一个极端变体,它不依赖任何标记的示例来学习任务。 这听起来令人难以置信,尤其是当使用示例学习是大多数监督学习算法所要解决的问题时。 零数据学习或零短学习方法在训练阶段本身进行了巧妙的调整,以利用附加信息来理解看不见的数据。 在他们的 *[Lear Lear* *ning* )一书中,Goodfellow 及其合作者提出了 z *eroshot 学习*作为学习三个变量的场景,例如 传统输入变量 *x* ,传统输出变量 *y* 以及描述任务的其他随机变量 *T* 。 因此,训练模型以学习 *P(y | x,T)*的条件概率分布。 零镜头学习在机器翻译等场景中非常有用,在这种情况下,我们甚至可能没有目标语言的标签。 + +# 转移学习的挑战 + +转移学习具有巨大的潜力,并且是现有学习算法通常需要的增强。 但是,与迁移学习相关的某些相关问题需要更多的研究和探索。 除了难以回答关于什么,什么时候以及如何转移的问题之外,负面转移和转移界限也带来了重大挑战。 + +# 负转移 + +到目前为止,我们讨论的案例都是基于源任务的知识转移来实现目标任务的改进。 在某些情况下,迁移学习会导致性能下降。 负向转移是指从源到目标的知识转移不会导致任何改善,而是导致目标任务的整体性能下降的情况。 否定转移的原因可能有多种,例如源任务与目标任务的关系不充分或转移方法不能很好地利用源任务和目标任务之间的关系的情况。 避免负向转移非常重要,需要仔细调查。 在他们的工作中,Rosenstien 及其合作者从经验上介绍了当源与目标过于不同时,暴力传递如何降低目标任务的性能。 正在研究 Bakker 及其合作者的贝叶斯方法,以及探索基于聚类的解决方案以识别相关性的其他技术,以避免产生负转移。 + +# 转移界限 + +在转移学习中量化转移也非常重要,这对转移的质量及其可行性具有影响。 为了衡量转移的数量,哈桑·马哈茂德(Hassan Mahmud)及其合作者使用 Kolmogorov 复杂度证明了一定的理论界限,以分析转移学习并衡量任务之间的相关性。 Eaton 及其合作者提出了一种新颖的基于图的方法来衡量知识转移。 这些技术的详细讨论超出了本书的范围。 鼓励读者使用本节概述的出版物来探讨这些主题。 + +# 摘要 + +在本书的第 1 章至第 3 章中设置了 ML 和深度学习的上下文和基础之后,本章开始了第二阶段的学习,即建立迁移学习的基础。 在深入研究实际用例之前,必须正式化对迁移学习的理解,并了解不同的技术和研究以及与之相关的挑战。 在本章中,我们介绍了迁移学习概念背后的基础知识,多年来的发展情况以及为什么首先需要迁移学习。 + +我们首先在学习算法及其相关优势的更广泛背景下理解迁移学习。 然后,我们讨论了用于理解,应用和分类迁移学习方法的各种策略。 在深度学习的背景下,转移学习是下一个讨论的主题,为本章的其余部分定下了基调。 我们讨论了与深度迁移学习相关的不同迁移学习方法,例如*特征提取*和*微调,*。 我们还介绍了著名的预训练模型和使用深度学习系统的迁移学习的流行应用。 近年来,深度学习已被证明是非常成功的,因此,在此领域中使用转移学习已进行了大量研究。 + +我们简要讨论了深度转移学习的不同变体,例如*域自适应*,*域混淆*,*多任务学习*,*单次学习*, 和*零击学习*。 我们通过介绍与迁移学习相关的挑战(例如负迁移和迁移边界)来结束本章。 在本章中,我们概述了与迁移学习相关的各种研究出版物和链接,并鼓励读者探索它们以获取更多信息。 本章将作为当前过渡学习领域的指导和概述。 敬请关注下一章的更多细节,我们将提供一些与转学相关的动手练习。 \ No newline at end of file diff --git a/docs/handson-tl-py/5.md b/docs/handson-tl-py/5.md new file mode 100644 index 0000000000000000000000000000000000000000..4d19befef985e1b04dce8cb6763e8a9c0cc11cf1 --- /dev/null +++ b/docs/handson-tl-py/5.md @@ -0,0 +1,1158 @@ +# 释放转移学习的力量 + +在上一章中,我们介绍了围绕转移学习的主要概念。 关键思想是,与从头开始构建自己的深度学习模型和体系结构相比,在各种任务中利用先进的,经过预训练的深度学习模型可产生更好的结果。 在本章中,我们将获得一个更动手的观点,即使用转移学习实际构建深度学习模型并将其应用于实际问题。 有无转移学习,我们将构建各种深度学习模型。 我们将分析它们的体系结构,并比较和对比它们的性能。 本章将涵盖以下主要方面: + +* 转移学习的需要 +* 从头开始构建**卷积神经网络**( **CNN** )模型: + * 建立基本的 CNN 模型 + * 通过正则化改进我们的 CNN 模型 + * 通过图像增强改善我们的 CNN 模型 +* 在预训练的 CNN 模型中利用转移学习: + * 使用预训练模型作为特征提取器 + * 通过图像增强改进我们的预训练模型 + * 通过微调改进我们的预训练模型 +* 模型性能评估 + +我们要感谢 Francois Chollet 不仅创建了令人惊叹的深度学习框架 Keras,还感谢他在他的书 *Python 深度学习*中谈到了有效学习迁移的现实世界问题。 在本章中,我们以此为灵感来刻画了迁移学习的真正力量。 本章的代码将在 GitHub 存储库中的[文件夹中提供,网址为](https://github.com/dipanjanS/hands-on-transfer-learning-with-python) [https://github.com/dipanjanS/hands-on-transfer-learning-with-python](https://github.com/dipanjanS/hands-on-transfer-learning-with-python) 根据需要遵循本章。 + +# 转移学习的需要 + +我们已经在[第 4 章](../Text/4.html),*转移学习基础*中简要讨论了转移学习的优势。 概括地说,与从头开始构建深度学习模型相比,我们获得了一些好处,例如,改善了基准性能,加快了整体模型的开发和培训时间,并且还获得了整体改进和优越的模型性能。 这里要记住的重要一点是,转移学习作为一个领域早已在深度学习之前就存在了,并且还可以应用于不需要深度学习的领域或问题。 + +现在让我们考虑一个现实世界的问题,在本章中,我们还将继续使用它来说明我们不同的深度学习模型,并在同一模型上利用转移学习。 您必须一次又一次听到深度学习的关键要求之一是,我们需要大量数据和样本来构建可靠的深度学习模型。 其背后的想法是模型可以从大量样本中自动学习特征。 但是,如果我们没有足够的训练样本并且要解决的问题仍然是一个相对复杂的问题,我们该怎么办? 例如,计算机视觉问题,例如图像分类,可能难以使用传统的统计技术或**机器学习**( **ML** )技术解决。 我们会放弃深度学习吗? + +考虑到**图像分类**问题,由于我们要处理的图像本质上是高维张量,因此拥有更多数据可使深度学习模型学习更好的图像基本特征表示。 但是,即使我们每个类别的图像样本的范围从几百到数千,基本的 CNN 模型在正确的体系结构和规范化条件下仍能正常运行。 这里要记住的关键点是,CNN 会学习与缩放,平移和旋转不变的模式和特征,因此我们在这里不需要自定义特征工程技术。 但是,我们可能仍然会遇到模型过度拟合之类的问题,我们将在本章稍后部分尝试解决这些问题。 + +关于迁移学习,已经在著名的 ImageNet 数据集( [http://image-net.org/about-overview](http://image-net.org/about-overview) )上训练了一些出色的预训练深度学习模型。 我们已经在[第 3 章](../Text/3.html)和*了解深度学习架构*中详细介绍了其中一些模型,本章将利用著名的`VGG-16`模型。 想法是使用通常是图像分类专家的预训练模型来解决我们的问题,即数据样本较少。 + +# 制定我们的现实问题 + +正如我们前面提到的,我们将在图像分类问题上进行工作,每个类别的训练样本数量较少。 我们的问题的数据集可在 Kaggle 上获得,它是其中最受欢迎的基于计算机视觉的数据集之一。 我们将使用的数据集来自**狗与猫**挑战( [https://www.kaggle.com/c/dogs-vs-cats/data](https://www.kaggle.com/c/dogs-vs-cats/data) ),而我们的 主要目标是建立一个可以成功识别图像并将其分类为猫或狗的模型。 就机器学习而言,这是一个基于图像的二进制分类问题。 + +首先,从数据集页面下载`train.zip`文件并将其存储在本地系统中。 下载后,将其解压缩到文件夹中。 该文件夹将包含 25,000 张猫和狗的图像; 即每个类别 12500 张图像。 + +# 建立我们的数据集 + +虽然我们可以使用所有 25,000 张图像并在它们上建立一些不错的模型,但是,如果您还记得的话,我们的问题目标包括增加的约束,即每类图像的数量很少。 为此,我们构建自己的数据集。 如果您想自己运行示例,可以参考`Datasets Builder.ipynb` Jupyter Notebook。 + +首先,我们加载以下依赖项,包括一个名为`utils`的实用程序模块,该模块在本章代码文件中的`utils.py`文件中可用。 当我们将图像复制到新文件夹时,这主要用于获得视觉进度条: + +```py +import glob +import numpy as np +import os +import shutil +from utils import log_progress + +np.random.seed(42) +``` + +现在,如下所示将所有图像加载到原始训练数据文件夹中: + +```py +files = glob.glob('train/*') + +cat_files = [fn for fn in files if 'cat' in fn] +dog_files = [fn for fn in files if 'dog' in fn] +len(cat_files), len(dog_files) + +Out [3]: (12500, 12500) +``` + +我们可以使用前面的输出来验证每个类别有 12,500 张图像。 现在,我们构建较小的数据集,以使我们有 3,000 张图像用于训练,1,000 张图像用于验证和 1,000 张图像用于我们的测试数据集(两个动物类别的表示均相同): + +```py +cat_train = np.random.choice(cat_files, size=1500, replace=False) +dog_train = np.random.choice(dog_files, size=1500, replace=False) +cat_files = list(set(cat_files) - set(cat_train)) +dog_files = list(set(dog_files) - set(dog_train)) + +cat_val = np.random.choice(cat_files, size=500, replace=False) +dog_val = np.random.choice(dog_files, size=500, replace=False) +cat_files = list(set(cat_files) - set(cat_val)) +dog_files = list(set(dog_files) - set(dog_val)) + +cat_test = np.random.choice(cat_files, size=500, replace=False) +dog_test = np.random.choice(dog_files, size=500, replace=False) + +print('Cat datasets:', cat_train.shape, cat_val.shape, cat_test.shape) +print('Dog datasets:', dog_train.shape, dog_val.shape, dog_test.shape) + +Cat datasets: (1500,) (500,) (500,) +Dog datasets: (1500,) (500,) (500,) +``` + +现在我们已经创建了数据集,让我们将它们写到单独文件夹中的磁盘中,以便我们将来可以在任何时候返回它们,而不必担心它们是否存在于主内存中: + +```py +train_dir = 'training_data' +val_dir = 'validation_data' +test_dir = 'test_data' + +train_files = np.concatenate([cat_train, dog_train]) +validate_files = np.concatenate([cat_val, dog_val]) +test_files = np.concatenate([cat_test, dog_test]) + +os.mkdir(train_dir) if not os.path.isdir(train_dir) else None +os.mkdir(val_dir) if not os.path.isdir(val_dir) else None +os.mkdir(test_dir) if not os.path.isdir(test_dir) else None + +for fn in log_progress(train_files, name='Training Images'): + shutil.copy(fn, train_dir) +for fn in log_progress(validate_files, name='Validation Images'): + shutil.copy(fn, val_dir) +for fn in log_progress(test_files, name='Test Images'): + shutil.copy(fn, test_dir) +``` + +一旦所有图像都复制到各自的目录中,以下屏幕快照中描述的进度条将变为绿色: + +![](img/3a4d72af-af1e-4c54-810d-6190c28141c9.png) + +# 制定我们的方法 + +由于这是图像分类问题,因此我们将利用 CNN 模型或 convNets 尝试解决此问题。 在本章开始时,我们简要讨论了我们的方法。 我们将从头开始构建简单的 CNN 模型,然后尝试使用正则化和图像增强等技术进行改进。 然后,我们将尝试利用预训练的模型来释放转学的真正力量! + +# 从头开始构建 CNN 模型 + +让我们开始构建图像分类分类器。 我们的方法是在训练数据集上建立模型,并在验证数据集上进行验证。 最后,我们将在测试数据集上测试所有模型的性能。 在进入建模之前,让我们加载并准备数据集。 首先,我们加载一些基本的依赖项: + +```py +import glob +import numpy as np +import matplotlib.pyplot as plt +from keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array, array_to_img + +%matplotlib inline +``` + +现在,使用以下代码片段加载数据集: + +```py +IMG_DIM = (150, 150) + +train_files = glob.glob('training_data/*') +train_imgs = [img_to_array(load_img(img, target_size=IMG_DIM)) for img + in train_files] +train_imgs = np.array(train_imgs) +train_labels = [fn.split('/')[1].split('.')[0].strip() for fn in + train_files] + +validation_files = glob.glob('validation_data/*') +validation_imgs = [img_to_array(load_img(img, target_size=IMG_DIM)) for + img in validation_files] +validation_imgs = np.array(validation_imgs) +validation_labels = [fn.split('/')[1].split('.')[0].strip() for fn in + validation_files] + +print('Train dataset shape:', train_imgs.shape, + 'tValidation dataset shape:', validation_imgs.shape) + +Train dataset shape: (3000, 150, 150, 3) +Validation dataset shape: (1000, 150, 150, 3) +``` + +我们可以清楚地看到我们有`3000`训练图像和`1000`验证图像。 每个图像的尺寸为 150 x 150,并具有用于红色,绿色和蓝色(RGB)的三个通道,因此为每个图像提供(`150`,`150`,`3`)尺寸。 现在,我们将像素值在(0,255)之间的每个图像缩放到(0,1)之间的值,因为深度学习模型在较小的输入值下确实可以很好地工作: + +```py +train_imgs_scaled = train_imgs.astype('float32') +validation_imgs_scaled = validation_imgs.astype('float32') +train_imgs_scaled /= 255 +validation_imgs_scaled /= 255 + +# visualize a sample image +print(train_imgs[0].shape) +array_to_img(train_imgs[0]) + +(150, 150, 3) +``` + +前面的代码生成以下输出: + +![](img/cec84552-8b64-4c85-a3c1-41ebd47bfb10.png) + +前面的输出显示了我们训练数据集中的示例图像之一。 现在,让我们设置一些基本的配置参数,并将文本类标签编码为数值(否则,Keras 将抛出错误): + +```py +batch_size = 30 +num_classes = 2 +epochs = 30 +input_shape = (150, 150, 3) + +# encode text category labels +from sklearn.preprocessing import LabelEncoder + +le = LabelEncoder() +le.fit(train_labels) +train_labels_enc = le.transform(train_labels) +validation_labels_enc = le.transform(validation_labels) + +print(train_labels[1495:1505], train_labels_enc[1495:1505]) + +['cat', 'cat', 'cat', 'cat', 'cat', 'dog', 'dog', 'dog', 'dog', 'dog'] +[0 0 0 0 0 1 1 1 1 1] +``` + +我们可以看到,我们的编码方案将`0`分配给`cat`标签,将`1`分配给`dog`标签。 现在,我们准备构建我们的第一个基于 CNN 的深度学习模型。 + +# 基本的 CNN 模型 + +我们将从建立具有三个卷积层的基本 CNN 模型开始,再加上用于从图像中自动提取特征的最大池化,以及对输出卷积特征图进行下采样。 要刷新有关卷积和池化层如何工作的记忆,请查看[第 3 章](../Text/3.html)和*了解深度学习架构*中的 CNN 部分。 + +提取这些特征图后,我们将使用一个密集层以及一个具有 S 型函数的输出层进行分类。 由于我们正在执行二进制分类,因此`binary_crossentropy`损失函数就足够了。 我们将使用流行的 RMSprop 优化器,该优化器可帮助我们使用反向传播来优化网络中单元的权重,从而使网络中的损失降到最低,从而得到一个不错的分类器。 请参阅[第 2 章](../Text/2.html),*深度学习要点*中的*随机梯度下降*和 *SGD 改进*部分,以获取深入的想法 有关优化器如何工作的信息。 简而言之,优化器(如 RMSprop)指定有关损耗梯度如何用于更新传递到我们网络的每批数据中的参数的规则。 + +让我们利用 Keras 并立即构建我们的 CNN 模型架构: + +```py +from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout +from keras.models import Sequential +from keras import optimizers + +model = Sequential() + +# convolution and pooling layers +model.add(Conv2D(16, kernel_size=(3, 3), activation='relu', + input_shape=input_shape)) +model.add(MaxPooling2D(pool_size=(2, 2))) +model.add(Conv2D(64, kernel_size=(3, 3), activation='relu')) +model.add(MaxPooling2D(pool_size=(2, 2))) +model.add(Conv2D(128, kernel_size=(3, 3), activation='relu')) +model.add(MaxPooling2D(pool_size=(2, 2))) + +model.add(Flatten()) +model.add(Dense(512, activation='relu')) +model.add(Dense(1, activation='sigmoid')) + +model.compile(loss='binary_crossentropy', + optimizer=optimizers.RMSprop(), + metrics=['accuracy']) + +model.summary() + +_________________________________________________________________ +Layer (type) Output Shape Param # +================================================================= +conv2d_1 (Conv2D) (None, 148, 148, 16) 448 +_________________________________________________________________ +max_pooling2d_1 (MaxPooling2 (None, 74, 74, 16) 0 +_________________________________________________________________ +conv2d_2 (Conv2D) (None, 72, 72, 64) 9280 +_________________________________________________________________ +max_pooling2d_2 (MaxPooling2 (None, 36, 36, 64) 0 +_________________________________________________________________ +conv2d_3 (Conv2D) (None, 34, 34, 128) 73856 +_________________________________________________________________ +max_pooling2d_3 (MaxPooling2 (None, 17, 17, 128) 0 +_________________________________________________________________ +flatten_1 (Flatten) (None, 36992) 0 +_________________________________________________________________ +dense_1 (Dense) (None, 512) 18940416 +_________________________________________________________________ +dense_2 (Dense) (None, 1) 513 +================================================================= + +Total params: 19,024,513 +Trainable params: 19,024,513 +Non-trainable params: 0 +``` + +前面的输出向我们展示了我们的基本 CNN 模型摘要。 就像我们之前提到的,我们使用三个卷积层进行特征提取。 平整层用于平整我们从第三卷积层获得的 17 x 17 特征图中的 128 个。 这被馈送到我们的密集层,以最终确定图像是狗(1)还是猫(0)。 所有这些都是模型训练过程的一部分,因此,让我们使用以下利用`fit(...)`功能的代码片段训练模型。 以下术语对于训练我们的模型非常重要: + +* `batch_size`表示每次迭代传递给模型的图像总数 +* 每次迭代后,将更新层中单位的权重 +* 迭代总数始终等于训练样本总数除以`batch_size` +* 一个时期是整个数据集一次通过网络,即所有迭代均基于数据批处理而完成 + +我们使用`30`的`batch_size`,我们的训练数据总共有 3,000 个样本,这表示每个纪元总共有 100 次迭代。 我们对模型进行了总共 30 个时期的训练,并因此在我们的 1,000 张图像的验证集上进行了验证: + +```py +history = model.fit(x=train_imgs_scaled, y=train_labels_enc, +validation_data=(validation_imgs_scaled, + validation_labels_enc), + batch_size=batch_size, + epochs=epochs, + verbose=1) + +Train on 3000 samples, validate on 1000 samples +Epoch 1/30 +3000/3000 - 10s - loss: 0.7583 - acc: 0.5627 - val_loss: 0.7182 - val_acc: 0.5520 +Epoch 2/30 +3000/3000 - 8s - loss: 0.6343 - acc: 0.6533 - val_loss: 0.5891 - val_acc: 0.7190 +... +... +Epoch 29/30 +3000/3000 - 8s - loss: 0.0314 - acc: 0.9950 - val_loss: 2.7014 - val_acc: 0.7140 +Epoch 30/30 +3000/3000 - 8s - loss: 0.0147 - acc: 0.9967 - val_loss: 2.4963 - val_acc: 0.7220 +``` + +根据训练和验证的准确性值,我们的模型似乎有点过拟合。 我们可以使用以下代码段绘制模型的准确性和误差,以获得更好的视角: + +```py +f, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4)) +t = f.suptitle('Basic CNN Performance', fontsize=12) +f.subplots_adjust(top=0.85, wspace=0.3) + +epoch_list = list(range(1,31)) +ax1.plot(epoch_list, history.history['acc'], label='Train Accuracy') +ax1.plot(epoch_list, history.history['val_acc'], label='Validation Accuracy') +ax1.set_xticks(np.arange(0, 31, 5)) +ax1.set_ylabel('Accuracy Value') +ax1.set_xlabel('Epoch') +ax1.set_title('Accuracy') +l1 = ax1.legend(loc="best") +``` + +```py +ax2.plot(epoch_list, history.history['loss'], label='Train Loss') +ax2.plot(epoch_list, history.history['val_loss'], label='Validation Loss') +ax2.set_xticks(np.arange(0, 31, 5)) +ax2.set_ylabel('Loss Value') +ax2.set_xlabel('Epoch') +ax2.set_title('Loss') +l2 = ax2.legend(loc="best") +``` + +以下图表利用了历史对象,其中包含每个时期的精度和损耗值: + +![](img/d38f922d-b1ed-41c4-888c-146a5fa4c0a1.png) + +您可以清楚地看到,在 2-3 个时期之后,模型开始对训练数据进行过度拟合。 我们在验证集中获得的平均准确度约为 **72%**,这不是一个不好的开始! 我们可以改进此模型吗? + +# 具有正则化的 CNN 模型 + +让我们通过增加一个卷积层,另一个密集的隐藏层来改进我们的基本 CNN 模型。 除此之外,我们将在每个隐藏的密集层之后添加 0.3 的差值以启用正则化。 我们在[第 2 章](../Text/2.html)和*深度学习基础知识*中简要介绍了辍学问题,因此随时可以快速浏览一下它,以备不时之需。 基本上,辍学是在深度神经网络中进行正则化的有效方法。 它可以分别应用于输入层和隐藏层。 + +通过将输出的输出设置为零,Dropout 随机掩盖了一部分设备的输出(在我们的示例中,它是密集层中 30%的设备的输出): + +```py +model = Sequential() +# convolutional and pooling layers +model.add(Conv2D(16, kernel_size=(3, 3), activation='relu', + input_shape=input_shape)) +model.add(MaxPooling2D(pool_size=(2, 2))) +model.add(Conv2D(64, kernel_size=(3, 3), activation='relu')) +model.add(MaxPooling2D(pool_size=(2, 2))) +model.add(Conv2D(128, kernel_size=(3, 3), activation='relu')) +model.add(MaxPooling2D(pool_size=(2, 2))) +model.add(Conv2D(128, kernel_size=(3, 3), activation='relu')) +model.add(MaxPooling2D(pool_size=(2, 2))) + +model.add(Flatten()) +model.add(Dense(512, activation='relu')) +model.add(Dropout(0.3)) +model.add(Dense(512, activation='relu')) +model.add(Dropout(0.3)) +model.add(Dense(1, activation='sigmoid')) + +model.compile(loss='binary_crossentropy', + optimizer=optimizers.RMSprop(), + metrics=['accuracy']) +``` + +现在,让我们在训练数据上训练新模型,并在验证数据集上验证其性能: + +```py +history = model.fit(x=train_imgs_scaled, y=train_labels_enc, + validation_data=(validation_imgs_scaled, + validation_labels_enc), + batch_size=batch_size, + epochs=epochs, + verbose=1) + +Train on 3000 samples, validate on 1000 samples +Epoch 1/30 +3000/3000 - 7s - loss: 0.6945 - acc: 0.5487 - val_loss: 0.7341 - val_acc: 0.5210 +Epoch 2/30 +3000/3000 - 7s - loss: 0.6601 - acc: 0.6047 - val_loss: 0.6308 - val_acc: 0.6480 +... +... +Epoch 29/30 +3000/3000 - 7s - loss: 0.0927 - acc: 0.9797 - val_loss: 1.1696 - val_acc: 0.7380 +Epoch 30/30 +3000/3000 - 7s - loss: 0.0975 - acc: 0.9803 - val_loss: 1.6790 - val_acc: 0.7840 +``` + +我们还要看看模型训练期间所有时期的准确性和损失值: + +![](img/3f0bcdb3-568a-48e2-8003-b2cd4f4f4b61.png) + +从前面的输出中您可以清楚地看到,尽管模型花费了更长的时间,但仍然最终使模型过度拟合,并且我们还获得了约 **78%**的更好的验证精度,这虽然不错,但并不令人惊讶。 + +模型过度拟合的原因是因为我们的训练数据少得多,并且模型在每个时期随着时间的推移不断看到相同的实例。 解决此问题的一种方法是利用图像增强策略,以与现有图像略有不同的图像来增强我们现有的训练数据。 我们将在下一节中详细介绍。 让我们暂时保存该模型,以便以后可以使用它来评估其在测试数据上的性能: + +```py +model.save('cats_dogs_basic_cnn.h5') +``` + +# 具有图像增强功能的 CNN 模型 + +让我们通过使用适当的图像增强策略添加更多数据来改进我们的常规 CNN 模型。 由于我们先前的模型每次都在相同的小数据点样本上进行训练,因此无法很好地推广,并在经过几个时期后最终过度拟合。 + +图像增强背后的想法是,我们遵循一个既定过程,从训练数据集中获取现有图像,并对它们应用一些图像变换操作,例如旋转,剪切,平移,缩放等,以生成新的,经过修改的版本 现有图像。 由于这些随机转换,我们每次都不会获得相同的图像,我们将利用 Python 生成器在训练过程中将这些新图像提供给我们的模型。 + +Keras 框架具有一个称为`ImageDataGenerator`的出色实用程序,可以帮助我们完成所有前面的操作。 让我们为训练和验证数据集初始化两个数据生成器: + +```py +train_datagen = ImageDataGenerator(rescale=1./255, zoom_range=0.3, + rotation_range=50, + width_shift_range=0.2, + height_shift_range=0.2, + shear_range=0.2, + horizontal_flip=True, + fill_mode='nearest') + +val_datagen = ImageDataGenerator(rescale=1./255) +``` + +`ImageDataGenerator`中有很多可用的选项,我们只是利用了其中一些。 随时查看 [https://keras.io/preprocessing/image/](https://keras.io/preprocessing/image/) 上的文档,以获取更详细的信息。 在我们的训练数据生成器中,我们获取原始图像,然后对它们执行几次转换以生成新图像。 其中包括: + +* 使用`zoom_range`参数将图像随机放大`0.3`倍。 +* 使用`rotation_range`参数将图像随机旋转`50`度。 +* 使用`width_shift_range`和`height_shift_range`参数,以图像宽度或高度的`0.2`因子水平或垂直地随机转换图像。 +* 使用`shear_range`参数随机应用基于剪切的变换。 +* 使用`horizontal_flip`参数在水平方向随机翻转一半图像。 +* 在应用任何前述操作(尤其是旋转或平移)之后,利用`fill_mode`参数为图像填充新像素。 在这种情况下,我们只用周围最近的像素值填充新像素。 + +让我们看看其中一些生成的图像可能看起来如何,以便您可以更好地理解它们。 我们将从训练数据集中获取两个样本图像进行说明。 第一张图片是猫的图片: + +```py +img_id = 2595 +cat_generator = train_datagen.flow(train_imgs[img_id:img_id+1], + train_labels[img_id:img_id+1], + batch_size=1) +cat = [next(cat_generator) for i in range(0,5)] +fig, ax = plt.subplots(1,5, figsize=(16, 6)) +print('Labels:', [item[1][0] for item in cat]) +l = [ax[i].imshow(cat[i][0][0]) for i in range(0,5)] +``` + +您可以在以下输出中清楚地看到,我们每次都会生成新版本的训练图像(具有平移,旋转和缩放),并且我们为其分配了一个`cat`标签,以便该模型可以从中提取相关特征 这些图像,还请记住,这些是猫: + +![](img/4dff20d1-9253-415f-8a18-62f217d9ddcc.png) + +让我们看一下现在是狗的图像: + +```py +img_id = 1991 +dog_generator = train_datagen.flow(train_imgs[img_id:img_id+1], + train_labels[img_id:img_id+1], + batch_size=1) +dog = [next(dog_generator) for i in range(0,5)] +fig, ax = plt.subplots(1,5, figsize=(15, 6)) +print('Labels:', [item[1][0] for item in dog]) +l = [ax[i].imshow(dog[i][0][0]) for i in range(0,5)] +``` + +这向我们展示了图像增强如何帮助创建新图像,以及在其上训练模型应如何帮助对抗过度拟合: + +![](img/47937375-d105-4389-89ce-20b30cfda764.png) + +请记住,对于我们的验证生成器,我们只需要将验证图像(原始图像)发送到模型以进行评估; 因此,我们仅缩放图像像素(介于 0-1 之间),并且不应用任何变换。 我们仅将图像增强转换应用于我们的训练图像: + +```py +train_generator = train_datagen.flow(train_imgs, train_labels_enc, + batch_size=30) +val_generator = val_datagen.flow(validation_imgs, + validation_labels_enc, + batch_size=20) + +input_shape = (150, 150, 3) +``` + +现在,使用我们创建的图像增强数据生成器来训练带有正则化的 CNN 模型。 我们将使用之前的相同模型架构: + +```py +from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout +from keras.models import Sequential +from keras import optimizers + +model = Sequential() +# convolution and pooling layers +model.add(Conv2D(16, kernel_size=(3, 3), activation='relu', + input_shape=input_shape)) +model.add(MaxPooling2D(pool_size=(2, 2))) +model.add(Conv2D(64, kernel_size=(3, 3), activation='relu')) +model.add(MaxPooling2D(pool_size=(2, 2))) +model.add(Conv2D(128, kernel_size=(3, 3), activation='relu')) +model.add(MaxPooling2D(pool_size=(2, 2))) +model.add(Conv2D(128, kernel_size=(3, 3), activation='relu')) +model.add(MaxPooling2D(pool_size=(2, 2))) + +model.add(Flatten()) +model.add(Dense(512, activation='relu')) +model.add(Dropout(0.3)) +model.add(Dense(512, activation='relu')) +model.add(Dropout(0.3)) +model.add(Dense(1, activation='sigmoid')) + +model.compile(loss='binary_crossentropy', + optimizer=optimizers.RMSprop(lr=1e-4), + metrics=['accuracy']) +``` + +我们在这里为优化器将默认学习率降低了 10 倍,以防止模型陷入局部最小值或过拟合,因为我们将发送大量具有随机变换的图像。 为了训练模型,我们现在需要稍微修改我们的方法,因为我们正在使用数据生成器。 我们将利用 Keras 的`fit_generator(...)`功能来训练该模型。 `train_generator`每次生成 30 张图像,因此我们将使用`steps_per_epoch`参数并将其设置为 100,以针对每个时期从训练数据中随机生成的 3,000 张图像上训练模型。 我们的`val_generator`每次生成 20 张图像,因此我们将`validation_steps`参数设置为 50,以在所有 1,000 张验证图像上验证我们的模型准确性(请记住,我们没有增加验证数据集): + +```py +history = model.fit_generator(train_generator, + steps_per_epoch=100, epochs=100, + validation_data=val_generator, + validation_steps=50, verbose=1) + +Epoch 1/100 +100/100 - 12s - loss: 0.6924 - acc: 0.5113 - val_loss: 0.6943 - val_acc: 0.5000 +Epoch 2/100 +100/100 - 11s - loss: 0.6855 - acc: 0.5490 - val_loss: 0.6711 - val_acc: 0.5780 +... +... +Epoch 99/100 +100/100 - 11s - loss: 0.3735 - acc: 0.8367 - val_loss: 0.4425 - val_acc: 0.8340 +Epoch 100/100 +100/100 - 11s - loss: 0.3733 - acc: 0.8257 - val_loss: 0.4046 - val_acc: 0.8200 +``` + +我们的验证准确度跃升至 **82%**左右,几乎比我们先前的模型好 **4-5%**。 此外,我们的训练准确性与验证准确性非常相似,这表明我们的模型不再适合。 下图描述了模型的准确性和每个时期的损失: + +![](img/82968e26-b880-4ddb-9fca-76ed6228eb7b.png) + +总体上,虽然验证准确性和损失存在一些峰值,但我们发现它与训练准确性非常接近,损失表明我们获得的模型与以前的模型相比,泛化效果更好。 现在保存此模型,以便稍后可以在测试数据集中对其进行评估: + +```py +model.save('cats_dogs_cnn_img_aug.h5') +``` + +现在,我们将尝试并利用迁移学习的功能,看看是否可以构建更好的模型。 + +# 利用预训练的 CNN 模型来利用转移学习 + +到目前为止,我们已经通过指定自己的架构从头开始构建了 CNN 深度学习模型。 在本节中,我们将利用预训练的模型,该模型基本上是计算机视觉领域的专家,并且在图像分类和归类中享有盛誉。 我们建议您阅读[第 4 章](../Text/4.html),*转移学习基础知识*,以简要地了解预训练模型及其在该领域中的应用。 + +在构建新模型或重用它们时,可以通过以下两种流行的方式来使用预训练的模型: + +* 使用预训练模型作为特征提取器 +* 微调预训练模型 + +我们将在本节中详细介绍这两个方面。 我们将在本章中使用的预训练模型是流行的 VGG-16 模型,该模型由牛津大学的视觉几何小组创建,该模型专门为大型视觉识别构建非常深的卷积网络。 您可以在 [http://www.robots.ox.ac.uk/~vgg/research/very_deep/](http://www.robots.ox.ac.uk/~vgg/research/very_deep/) 中找到有关它的更多信息。 **ImageNet 大规模视觉识别挑战赛**( **ILSVRC** )评估了用于大规模物体检测和图像分类的算法,其模型通常在这场比赛中获得第一名。 + +像 VGG-16 这样的预训练模型是已经在具有大量不同图像类别的巨大数据集(ImageNet)上进行训练的模型。 考虑到这一事实,正如我们之前针对 CNN 模型所学习的特征所讨论的那样,该模型应该已经学习了稳健的特征层次结构,即空间,旋转和平移不变性。 因此,该模型已经学会了对属于 1,000 个不同类别的一百万个图像的特征的良好表示,可以充当适合于计算机视觉问题的新图像的良好特征提取器。 这些新图像可能永远不会存在于 ImageNet 数据集中或可能属于完全不同的类别,但考虑到我们在[第 4 章](../Text/4.html)中讨论的转移学习原理,该模型仍应能够从这些图像中提取相关特征。 ],*迁移学习基础*。 + +这为我们提供了一个优势,即可以使用预先训练的模型作为新图像的有效特征提取器,以解决各种复杂的计算机视觉任务,例如用较少的图像解决我们的猫对狗分类器,甚至构建狗的品种分类器,面部表情分类器 , 以及更多! 在释放转移学习的力量解决我们的问题之前,让我们简要讨论一下 VGG-16 模型架构。 + +# 了解 VGG-16 模型 + +VGG-16 模型是建立在 ImageNet 数据库上的 16 层(卷积和完全连接)网络,该网络旨在进行图像识别和分类。 该模型是由 Karen Simonyan 和 Andrew Zisserman 建立的,并在他们的论文*用于大规模图像识别的超深度卷积网络*,arXiv(2014)(在 [https:// arxiv 上提供)中提到。 org / pdf / 1409.1556.pdf](https://arxiv.org/pdf/1409.1556.pdf) 。 + +我建议所有感兴趣的读者继续阅读本文中的优秀文献。 在[第 3 章](../Text/3.html)和*了解深度学习架构*中简要提到了 VGG-16 模型,但我们将对其进行更详细的讨论,并在我们的示例中也使用它。 下图描述了 VGG-16 模型的体系结构: + +![](img/fd570bbe-898c-40aa-b18b-a884739d3c8d.png) + +您可以清楚地看到,我们总共有 13 个卷积层,其中使用了 3 x 3 卷积滤波器,以及用于下采样的最大池化层,每层中总共有两个完全连接的隐藏层,共 4,096 个单元,然后是 1,000 个单元的密集层, 其中每个单位代表 ImageNet 数据库中的图像类别之一。 + +我们不需要最后三层,因为我们将使用我们自己的完全连接的密集层来预测图像是狗还是猫。 我们更关注前五个块,因此我们可以利用 VGG 模型作为有效的特征提取器。 对于其中一个模型,我们将冻结所有五个卷积块以确保它们的权重在每个时期后都不会更新,从而将其用作简单的特征提取器。 对于最后一个模型,我们将对 VGG 模型进行微调,在该模型中,我们将解冻最后两个块(**块 4** 和**块 5** ),以便它们的权重在 我们训练自己的模型时的每个时期(每批数据)。 + +在下面的框图中,我们代表了先前的体系结构以及将要使用的两个变体(基本特征提取器和微调),因此您可以获得更好的视觉视角: + +![](img/e5207a55-3734-47f0-8c63-9ce3427c1841.png) + +因此,我们最关心的是利用 VGG-16 模型的卷积块,然后展平最终输出(来自特征图),以便我们可以将其输入到我们自己的密集层中进行分类。 本章本节中使用的所有代码都可以通过`Transfer Learning.ipynb` Jupyter Notebook 在 CNN 中找到。 + +# 预训练的 CNN 模型作为特征提取器 + +让我们利用 Keras,加载 VGG-16 模型并冻结卷积块,以便将其用作图像特征提取器: + +```py +from keras.applications import vgg16 +from keras.models import Model +import keras + +vgg = vgg16.VGG16(include_top=False, weights='imagenet', + input_shape=input_shape) + +output = vgg.layers[-1].output +output = keras.layers.Flatten()(output) +vgg_model = Model(vgg.input, output) +vgg_model.trainable = False + +for layer in vgg_model.layers: + layer.trainable = False + +vgg_model.summary() + +_________________________________________________________________ +Layer (type) Output Shape Param # +================================================================= +input_1 (InputLayer) (None, 150, 150, 3) 0 +_________________________________________________________________ +block1_conv1 (Conv2D) (None, 150, 150, 64) 1792 +_________________________________________________________________ +block1_conv2 (Conv2D) (None, 150, 150, 64) 36928 +_________________________________________________________________ +block1_pool (MaxPooling2D) (None, 75, 75, 64) 0 +_________________________________________________________________ +block2_conv1 (Conv2D) (None, 75, 75, 128) 73856 +_________________________________________________________________ +block2_conv2 (Conv2D) (None, 75, 75, 128) 147584 +_________________________________________________________________ +block2_pool (MaxPooling2D) (None, 37, 37, 128) 0 +_________________________________________________________________ +block3_conv1 (Conv2D) (None, 37, 37, 256) 295168 +_________________________________________________________________ +block3_conv2 (Conv2D) (None, 37, 37, 256) 590080 +_________________________________________________________________ +block3_conv3 (Conv2D) (None, 37, 37, 256) 590080 +_________________________________________________________________ +block3_pool (MaxPooling2D) (None, 18, 18, 256) 0 +_________________________________________________________________ +block4_conv1 (Conv2D) (None, 18, 18, 512) 1180160 +_________________________________________________________________ +block4_conv2 (Conv2D) (None, 18, 18, 512) 2359808 +_________________________________________________________________ +block4_conv3 (Conv2D) (None, 18, 18, 512) 2359808 +_________________________________________________________________ +block4_pool (MaxPooling2D) (None, 9, 9, 512) 0 +_________________________________________________________________ +block5_conv1 (Conv2D) (None, 9, 9, 512) 2359808 +_________________________________________________________________ +block5_conv2 (Conv2D) (None, 9, 9, 512) 2359808 +_________________________________________________________________ +block5_conv3 (Conv2D) (None, 9, 9, 512) 2359808 +_________________________________________________________________ +block5_pool (MaxPooling2D) (None, 4, 4, 512) 0 +_________________________________________________________________ +flatten_1 (Flatten) (None, 8192) 0 +================================================================= +Total params: 14,714,688 +Trainable params: 0 +Non-trainable params: 14,714,688 +__________________________________________________________________ +``` + +该模型摘要向我们显示了每个块以及每个块中存在的层,这些层与我们之前描述的体系结构图匹配。 您会看到我们删除了与 VGG-16 模型有关的分类器的最后一部分,因为我们将构建自己的分类器并利用 VGG 作为特征提取器。 + +要验证 VGG-16 模型的各层是否冻结,我们可以使用以下代码: + +```py +import pandas as pd +pd.set_option('max_colwidth', -1) + +layers = [(layer, layer.name, layer.trainable) for layer in + vgg_model.layers] +pd.DataFrame(layers, columns=['Layer Type', 'Layer Name', 'Layer + Trainable']) +``` + +前面的代码生成以下输出: + +![](img/86e35b8e-9cc4-4655-8d61-5b3fefdd5d52.png) + +```py +print("Trainable layers:", vgg_model.trainable_weights) +Trainable layers: [] +``` + +从前面的输出中很明显,VGG-16 模型的所有层都是冻结的,这很好,因为我们不希望在模型训练期间改变它们的权重。 VGG-16 模型中的最后一个激活特征图(`block5_pool`的输出)为我们提供了瓶颈特征,这些特征可以被展平并馈送到完全连接的深度神经网络分类器中。 以下代码片段显示了来自我们的训练数据的样本图像的瓶颈特征: + +```py +bottleneck_feature_example = vgg.predict(train_imgs_scaled[0:1]) print(bottleneck_feature_example.shape) +plt.imshow(bottleneck_feature_example[0][:,:,0]) + +(1, 4, 4, 512) +``` + +前面的代码生成以下输出: + +![](img/b5e2f78f-af3a-4480-b5c1-745bfeea9898.png) + +我们将`vgg_model`对象中的瓶颈特征展平,以使其可以被馈送到我们完全连接的分类器中。 节省模型训练时间的一种方法是使用该模型,并从我们的训练和验证数据集中提取所有特征,然后将它们作为输入提供给分类器。 现在,让我们从培训和验证集中提取瓶颈功能: + +```py +def get_bottleneck_features(model, input_imgs): + features = model.predict(input_imgs, verbose=0) + return features + +train_features_vgg = get_bottleneck_features(vgg_model, + train_imgs_scaled) +validation_features_vgg = get_bottleneck_features(vgg_model, + validation_imgs_scaled) + +print('Train Bottleneck Features:', train_features_vgg.shape, + '\tValidation Bottleneck Features:', + validation_features_vgg.shape) + +Train Bottleneck Features: (3000, 8192) Validation Bottleneck Features: + (1000, 8192) +``` + +前面的输出告诉我们,我们已经成功提取了 3,000 个训练图像和 1,000 个验证图像的尺寸为 1 x 8,192 的扁平瓶颈特征。 现在让我们构建深度神经网络分类器的体系结构,它将这些功能作为输入: + +```py +from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, InputLayer +from keras.models import Sequential +from keras import optimizers + +input_shape = vgg_model.output_shape[1] +model = Sequential() +model.add(InputLayer(input_shape=(input_shape,))) +model.add(Dense(512, activation='relu', input_dim=input_shape)) model.add(Dropout(0.3)) model.add(Dense(512, activation='relu')) model.add(Dropout(0.3)) model.add(Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', + optimizer=optimizers.RMSprop(lr=1e-4), + metrics=['accuracy']) + +model.summary() +_______________________________________________________________ +Layer (type) Output Shape Param # ================================================================= +input_2 (InputLayer) (None, 8192) 0 _________________________________________________________________ +dense_1 (Dense) (None, 512) 4194816 _________________________________________________________________ +dropout_1 (Dropout) (None, 512) 0 _________________________________________________________________ +dense_2 (Dense) (None, 512) 262656 _________________________________________________________________ +dropout_2 (Dropout) (None, 512) 0 _________________________________________________________________ +dense_3 (Dense) (None, 1) 513 ================================================================= +``` + +就像我们之前提到的,大小为`8192`的瓶颈特征向量用作我们分类模型的输入。 关于密集层,我们使用与以前的模型相同的体系结构。 让我们现在训练这个模型: + +```py +history = model.fit(x=train_features_vgg, y=train_labels_enc, + validation_data=(validation_features_vgg, + validation_labels_enc), + batch_size=batch_size, epochs=epochs, verbose=1) + +Train on 3000 samples, validate on 1000 samples +Epoch 1/30 +3000/3000 - 1s 373us/step - loss: 0.4325 - acc: 0.7897 - val_loss: 0.2958 - val_acc: 0.8730 +Epoch 2/30 +3000/3000 - 1s 286us/step - loss: 0.2857 - acc: 0.8783 - val_loss: 0.3294 - val_acc: 0.8530 +... +... +Epoch 29/30 +3000/3000 - 1s 287us/step - loss: 0.0121 - acc: 0.9943 - val_loss: 0.7760 - val_acc: 0.8930 +Epoch 30/30 +3000/3000 - 1s 287us/step - loss: 0.0102 - acc: 0.9987 - val_loss: 0.8344 - val_acc: 0.8720 +``` + +我们得到的模型的验证精度接近 **88%**,几乎比具有图像增强功能的基本 CNN 模型提高了 **5-6%**,这非常好。 不过,该模型似乎确实过拟合,我们可以使用下图中所示的精度和损耗图进行检查: + +![](img/ae38af80-e7a5-45ff-a1b9-d7991de028b6.png) + +在第五个时期之后,模型训练与验证准确性之间存在相当大的差距,这清楚表明模型在此之后对训练数据过度拟合。 但是总的来说,这似乎是迄今为止最好的模型,通过利用 VGG-16 模型作为特征提取器,我们甚至不需要使用图像增强策略就可以接近 **90%**验证精度。 但是我们还没有充分利用迁移学习的全部潜力。 让我们尝试在此模型上使用我们的图像增强策略。 在此之前,我们使用以下代码将此模型保存到磁盘: + +```py +model.save('cats_dogs_tlearn_basic_cnn.h5') +``` + +# 预训练的 CNN 模型作为具有图像增强的特征提取器 + +我们将为之前使用的训练和验证数据集使用相同的数据生成器。 为了便于理解,构建它们的代码如下所示: + +```py +train_datagen = ImageDataGenerator(rescale=1./255, zoom_range=0.3, + rotation_range=50, + width_shift_range=0.2, + height_shift_range=0.2, + shear_range=0.2, + horizontal_flip=True, + fill_mode='nearest') + +val_datagen = ImageDataGenerator(rescale=1./255) +train_generator = train_datagen.flow(train_imgs, train_labels_enc, + batch_size=30) +val_generator = val_datagen.flow(validation_imgs, + validation_labels_enc, + batch_size=20) +``` + +现在让我们构建深度学习模型架构。 因为我们将在数据生成器上进行培训,所以我们不会像上次那样提取瓶颈功能。 因此,我们将`vgg_model`对象作为输入传递给我们自己的模型: + +```py +model = Sequential() + +model.add(vgg_model) +model.add(Dense(512, activation='relu', input_dim=input_shape)) model.add(Dropout(0.3)) model.add(Dense(512, activation='relu')) model.add(Dropout(0.3)) model.add(Dense(1, activation='sigmoid')) + +model.compile(loss='binary_crossentropy', + optimizer=optimizers.RMSprop(lr=2e-5), + metrics=['accuracy']) +``` + +您可以清楚地看到一切都一样。 由于我们将训练 100 个时代,因此我们将学习率稍微降低了,并且不想对我们的模型层进行突然的权重调整。 请记住,VGG-16 模型的图层仍在此处冻结,我们仍将其仅用作基本特征提取器: + +```py +history = model.fit_generator(train_generator, steps_per_epoch=100, + epochs=100, + validation_data=val_generator, + validation_steps=50, + verbose=1) + +Epoch 1/100 +100/100 - 45s 449ms/step - loss: 0.6511 - acc: 0.6153 - val_loss: 0.5147 - val_acc: 0.7840 +Epoch 2/100 +100/100 - 41s 414ms/step - loss: 0.5651 - acc: 0.7110 - val_loss: 0.4249 - val_acc: 0.8180 +... +... +Epoch 99/100 +100/100 - 42s 417ms/step - loss: 0.2656 - acc: 0.8907 - val_loss: 0.2757 - val_acc: 0.9050 +Epoch 100/100 +100/100 - 42s 418ms/step - loss: 0.2876 - acc: 0.8833 - val_loss: 0.2665 - val_acc: 0.9000 +``` + +我们可以看到我们的模型的整体验证精度为 **90%**,这比我们先前的模型略有改进,并且训练和验证精度彼此非常接近,表明该模型是 不适合。 可以通过查看以下有关模型准确性和损失的图来加强这一点: + +![](img/14428a54-dea9-4ffd-9a3e-3173cedb5907.png) + +我们可以清楚地看到,训练值和验证准确性的值非常接近,并且模型也不会过拟合。 此外,我们达到 **90%**的准确性,这很干净! 让我们现在将此模型保存在磁盘上,以便将来对测试数据进行评估: + +```py +model.save('cats_dogs_tlearn_img_aug_cnn.h5') +``` + +现在,我们将微调 VGG-16 模型以构建我们的最后一个分类器,我们将在此取消冻结第 4 块和第 5 块,如本节开头所述。 + +# 具有微调和图像增强功能的预训练 CNN 模型 + +现在,我们将利用存储在`vgg_model`变量中的 VGG-16 模型对象,解冻卷积块 4 和 5,同时保持前三个块处于冻结状态。 以下代码可帮助我们实现这一目标: + +```py +vgg_model.trainable = True +set_trainable = False + +for layer in vgg_model.layers: + if layer.name in ['block5_conv1', 'block4_conv1']: + set_trainable = True + if set_trainable: + layer.trainable = True + else: + layer.trainable = False + +print("Trainable layers:", vgg_model.trainable_weights) + +Trainable layers: +[, , +, , +, , +, , +, , +, ] +``` + +您可以从前面的输出中清楚地看到,与块 4 和 5 有关的卷积和池化层现在是可训练的,并且还可以使用以下代码来验证冻结和解冻哪些层: + +```py +layers = [(layer, layer.name, layer.trainable) for layer in vgg_model.layers] pd.DataFrame(layers, columns=['Layer Type', 'Layer + Name', 'Layer Trainable']) +``` + +前面的代码生成以下输出: + +![](img/42d93da8-d1e5-45c3-9c03-83ace0fcf7d7.png) + +我们可以清楚地看到最后两个块现在是可训练的,这意味着当我们传递每批数据时,这些层的权重也将在每个纪元中通过反向传播进行更新。 我们将使用与之前的模型相同的数据生成器和模型架构,并对模型进行训练。 因为我们不想卡在任何局部最小值上,所以我们会稍微降低学习率,并且我们也不想突然将可训练的 VGG-16 模型层的权重突然增加可能会对模型产生不利影响的大因素: + +```py +# data generators +train_datagen = ImageDataGenerator(rescale=1./255, zoom_range=0.3, + rotation_range=50, + width_shift_range=0.2, + height_shift_range=0.2, + shear_range=0.2, + horizontal_flip=True, + fill_mode='nearest') + +val_datagen = ImageDataGenerator(rescale=1./255) + +train_generator = train_datagen.flow(train_imgs, train_labels_enc, + batch_size=30) +val_generator = val_datagen.flow(validation_imgs, + validation_labels_enc, + batch_size=20) + +# build model architecture +model = Sequential() + +model.add(vgg_model) +model.add(Dense(512, activation='relu', input_dim=input_shape)) model.add(Dropout(0.3)) model.add(Dense(512, activation='relu')) model.add(Dropout(0.3)) model.add(Dense(1, activation='sigmoid')) + +model.compile(loss='binary_crossentropy', + optimizer=optimizers.RMSprop(lr=1e-5), + metrics=['accuracy']) + +# model training +history = model.fit_generator(train_generator, steps_per_epoch=100, + epochs=100, + validation_data=val_generator, + validation_steps=50, + verbose=1) + +Epoch 1/100 +100/100 - 64s 642ms/step - loss: 0.6070 - acc: 0.6547 - val_loss: 0.4029 - val_acc: 0.8250 +Epoch 2/100 +100/100 - 63s 630ms/step - loss: 0.3976 - acc: 0.8103 - val_loss: 0.2273 - val_acc: 0.9030 +... +... +Epoch 99/100 +100/100 - 63s 629ms/step - loss: 0.0243 - acc: 0.9913 - val_loss: 0.2861 - val_acc: 0.9620 +Epoch 100/100 +100/100 - 63s 629ms/step - loss: 0.0226 - acc: 0.9930 - val_loss: 0.3002 - val_acc: 0.9610 +``` + +从前面的输出中我们可以看到,我们的模型的验证精度约为 **96%**,这比我们先前的模型提高了 **6%**。 总体而言,与我们的第一个基本 CNN 模型相比,该模型的验证准确性提高了 **24%**。 这确实显示了转移学习可以多么有用。 + +让我们观察模型的准确性和损失图: + +![](img/5c23c579-af88-4668-90a6-ae5faf899502.png) + +我们可以看到,这里的准确性值确实非常好,尽管模型看起来可能对训练数据有些过拟合,但我们仍然获得了很高的验证准确性。 现在,使用以下代码将此模型保存到磁盘: + +```py +model.save('cats_dogs_tlearn_finetune_img_aug_cnn.h5') +``` + +现在,通过在测试数据集上实际评估模型的性能,将所有模型进行测试。 + +# 评估我们的深度学习模型 + +现在,我们将评估到目前为止构建的五个不同模型,方法是首先在样本测试图像上对其进行测试,然后可视化 CNN 模型实际上是如何尝试从图像中分析和提取特征,最后通过在测试数据集上测试每个模型的性能来进行评估 。 如果要执行代码并遵循本章的内容,`Model Performance Evaluations.ipynb` Jupyter Notebook 中提供了此部分的代码。 我们还构建了一个名为`model_evaluation_utils`的实用工具模块,我们将使用该模块来评估深度学习模型的性能。 让我们在开始之前加载以下依赖项: + +```py +import glob +import numpy as np +import matplotlib.pyplot as plt +from keras.preprocessing.image import load_img, img_to_array, array_to_img +from keras.models import load_model +import model_evaluation_utils as meu + +%matplotlib inline +``` + +加载这些依赖关系后,让我们加载到目前为止已保存的模型: + +```py +basic_cnn = load_model('cats_dogs_basic_cnn.h5') +img_aug_cnn = load_model('cats_dogs_cnn_img_aug.h5') +tl_cnn = load_model('cats_dogs_tlearn_basic_cnn.h5') +tl_img_aug_cnn = load_model('cats_dogs_tlearn_img_aug_cnn.h5') tl_img_aug_finetune_cnn = + load_model('cats_dogs_tlearn_finetune_img_aug_cnn.h5') +``` + +这有助于我们检索使用各种技术和体系结构在本章中创建的所有五个模型。 + +# 对样本测试图像的模型预测 + +现在,我们将加载不属于任何数据集的样本图像,并尝试查看不同模型的预测。 我将在此处使用我的宠物猫的图像,因此这将很有趣! 让我们加载示例图像和一些基本配置: + +```py +# basic configurations +IMG_DIM = (150, 150) +input_shape = (150, 150, 3) +num2class_label_transformer = lambda l: ['cat' if x == 0 else 'dog' for + x in l] +class2num_label_transformer = lambda l: [0 if x == 'cat' else 1 for x + in l] + +# load sample image +sample_img_path = 'my_cat.jpg' +sample_img = load_img(sample_img_path, target_size=IMG_DIM) +sample_img_tensor = img_to_array(sample_img) +sample_img_tensor = np.expand_dims(sample_img_tensor, axis=0) +sample_img_tensor /= 255\. +print(sample_img_tensor.shape) +plt.imshow(sample_img_tensor[0]) (1, 150, 150, 3) +``` + +前面的代码生成以下输出: + +![](img/04d75f26-123b-4862-ab2a-fcb28b932d36.png) + +现在,我们已经加载了示例图像,让我们看看我们的模型将其作为该图像类别的预测(我的猫): + +```py +cnn_prediction = num2class_label_transformer(basic_cnn.predict_classes( + sample_img_tensor, + verbose=0)) +cnn_img_aug_prediction = num2class_label_transformer(img_aug_cnn.predict_classes( + sample_img_tensor, + verbose=0)) +tlearn_cnn_prediction = num2class_label_transformer(tl_cnn.predict_classes( + get_bottleneck_features(vgg_model, + sample_img_tensor), + verbose=0)) +tlearn_cnn_img_aug_prediction = +num2class_label_transformer( + tl_img_aug_cnn.predict_classes(sample_img_tensor, + verbose=0)) +tlearn_cnn_finetune_img_aug_prediction = +num2class_label_transformer( + tl_img_aug_finetune_cnn.predict_classes(sample_img_tensor, + verbose=0)) + +print('Predictions for our sample image:\n', + '\nBasic CNN:', cnn_prediction, + '\nCNN with Img Augmentation:', cnn_img_aug_prediction, + '\nPre-trained CNN (Transfer Learning):', tlearn_cnn_prediction, + '\nPre-trained CNN with Img Augmentation (Transfer Learning):', + tlearn_cnn_img_aug_prediction, + '\nPre-trained CNN with Fine-tuning & Img Augmentation (Transfer + Learning):', tlearn_cnn_finetune_img_aug_prediction) + +Predictions for our sample image: Basic CNN: ['cat'] +CNN with Img Augmentation: ['dog'] +Pre-trained CNN (Transfer Learning): ['dog'] +Pre-trained CNN with Img Augmentation (Transfer Learning): ['cat'] +Pre-trained CNN with Fine-tuning & Img Augmentation (Transfer Learning): ['cat'] +``` + +您可以从前面的输出中看到,我们的三个模型像猫一样正确地预测了图像,其中两个错误了。 有趣的是,基本的 CNN 模型也正确无误,并且预训练的模型具有预期的微调和图像增强功能。 + +# 可视化 CNN 模型的感知 + +深度学习模型通常被称为**黑盒模型**,因为与诸如决策树之类的简单 ML 模型相比,很难真正解释该模型在内部的工作方式。 我们知道,基于 CNN 的深度学习模型使用卷积层,该卷积层使用过滤器提取代表特征空间层次的激活特征图。 从概念上讲,顶级卷积层学习小的局部模式,而网络中较低的层则学习更复杂和更大的模式,这些模式是从顶级卷积层获得的。 让我们尝试通过一个示例来形象化。 + +我们将采用最佳模型(通过微调和图像增强进行转移学习),并尝试从前八层中提取输出激活特征图。 本质上,这将最终为我们提供 VGG-16 模型前三个模块的卷积和池化层,因为我们在模型中使用了相同的特征提取。 + +要查看这些层,可以使用以下代码: + +```py +tl_img_aug_finetune_cnn.layers[0].layers[1:9] + +[, , , , , , , ] +``` + +现在,基于它试图从我的猫的样本测试图像中提取的内容,从我们的模型中提取特征图。 为了简单说明,我们从块 1 中提取第一个卷积层之后的输出,并在以下代码段中从中查看一些激活特征图: + +```py +from keras import models + +# Extracts the outputs of the top 8 layers: +layer_outputs = [layer.output for layer in + tl_img_aug_finetune_cnn.layers[0].layers[1:9]] + +# Creates a model that will return these outputs, given the model input: activation_model = models.Model( + inputs=tl_img_aug_finetune_cnn.layers[0].layers[1].input, + outputs=layer_outputs) + +# This will return a list of 8 Numpy arrays +# one array per layer activation +activations = activation_model.predict(sample_img_tensor) +print('Sample layer shape:', activations[0].shape) +print('Sample convolution (activation map) shape:', + activations[0][0, :, :, 1].shape) + +fig, ax = plt.subplots(1,5, figsize=(16, 6)) +ax[0].imshow(activations[0][0, :, :, 10], cmap='bone') ax[1].imshow(activations[0][0, :, :, 25], cmap='bone') ax[2].imshow(activations[0][0, :, :, 40], cmap='bone') ax[3].imshow(activations[0][0, :, :, 55], cmap='bone') ax[4].imshow(activations[0][0, :, :, 63], cmap='bone') + +Sample layer shape: (1, 150, 150, 64) +Sample convolution (activation map) shape: (150, 150) +``` + +前面的代码生成以下输出: + +![](img/6652c604-0dbe-4ce6-abe2-979daa464c24.png) + +从前面的输出中,我们可以清楚地看到,第一卷积层的输出为我们提供了总共 64 个激活特征图,每个特征图的大小为 150 x 150。 我们在前面的代码段中可视化了其中五个特征图。 您可以看到模型如何尝试提取与图像有关的相关特征,例如色相,强度,边缘,角等。 以下输出描绘了来自块 1 和 2 的 VGG-16 模型的更多激活图: + +![](img/1f407278-f56d-4d09-b2e1-ceb9ea3ad9e5.png) + +为了获得上述激活功能图,我们利用了`Model Performance Evaluations.ipynb` Jupyter Notebook 中可用的代码段,这要感谢 Francois Chollet 和他的书 *Python 深度学习*,它可以帮助可视化所有选定的 在我们的 CNN 模型中 我们已经可视化了模型的前八层,这是我们在笔记本中较早选择的,但是在这里显示了前两个块的激活图。 随意检出笔记本并为自己的模型重复使用相同的代码。 从前面的屏幕截图中,您可能会看到顶层要素图通常保留了很多原始图像,但是当您深入模型时,要素图变得更加抽象,复杂且难以解释。 + +# 测试数据的评估模型性能 + +现在是时候进行最终测试了,在该测试中,我们通过对测试数据集进行预测来从字面上测试模型的性能。 在进行预测之前,让我们先加载并准备测试数据集: + +```py +IMG_DIM = (150, 150) +test_files = glob.glob('test_data/*') +test_imgs = [img_to_array(load_img(img, target_size=IMG_DIM)) + for img in test_files] +test_imgs = np.array(test_imgs) + +test_labels = [fn.split('/')[1].split('.')[0].strip() for fn in test_files] test_labels_enc = class2num_label_transformer(test_labels) +test_imgs_scaled = test_imgs.astype('float32') +test_imgs_scaled /= 255 + +print('Test dataset shape:', test_imgs.shape) + +Test dataset shape: (1000, 150, 150, 3) +``` + +现在我们已经准备好按比例缩放的数据集,让我们通过对所有测试图像进​​行预测来评估每个模型,然后通过检查预测的准确性来评估模型的性能: + +```py +# Model 1 - Basic CNN +predictions = basic_cnn.predict_classes(test_imgs_scaled, verbose=0) +predictions = num2class_label_transformer(predictions) meu.display_model_performance_metrics(true_labels=test_labels, + predicted_labels=predictions, + classes=list(set(test_labels))) +``` + +前面的代码生成以下输出: + +![](img/87483eb5-1c35-4109-8e55-167fad61e513.png) + +```py +# Model 2 - Basic CNN with Image Augmentation +predictions = img_aug_cnn.predict_classes(test_imgs_scaled, verbose=0) predictions = num2class_label_transformer(predictions) meu.display_model_performance_metrics(true_labels=test_labels, + predicted_labels=predictions, + classes=list(set(test_labels))) +``` + +前面的代码生成以下输出: + +![](img/db437583-5d9f-45a6-8743-2f82b51f79ac.png) + +```py +# Model 3 - Transfer Learning (basic feature extraction) +test_bottleneck_features = get_bottleneck_features(vgg_model, test_imgs_scaled) predictions = tl_cnn.predict_classes(test_bottleneck_features, verbose=0) predictions = num2class_label_transformer(predictions) + +meu.display_model_performance_metrics(true_labels=test_labels, + predicted_labels=predictions, + classes=list(set(test_labels))) +``` + +前面的代码生成以下输出: + +![](img/ee23ffeb-3522-48a2-be49-14afc13b75b4.png) + +```py +# Model 4 - Transfer Learning with Image Augmentation +predictions = tl_img_aug_cnn.predict_classes(test_imgs_scaled, verbose=0) predictions = num2class_label_transformer(predictions) meu.display_model_performance_metrics(true_labels=test_labels, + predicted_labels=predictions, + classes=list(set(test_labels))) +``` + +前面的代码生成以下输出: + +![](img/5109e69b-4a8d-42ea-995e-728f0130f11d.png) + +```py +# Model 5 - Transfer Learning with Fine-tuning & Image Augmentation +predictions = tl_img_aug_finetune_cnn.predict_classes(test_imgs_scaled, + verbose=0) +predictions = num2class_label_transformer(predictions) meu.display_model_performance_metrics(true_labels=test_labels, + predicted_labels=predictions, + classes=list(set(test_labels))) +``` + +![](img/713a7c6d-264b-4ba3-b7d9-6c85f31cf868.png) + +我们可以看到我们肯定有一些有趣的结果。 每个后续模型的性能均优于先前模型,这是预期的,因为我们对每个新模型都尝试了更高级的技术。 我们最差的模型是基本的 CNN 模型,其模型准确度和 F1-分数约为 **78%**,而我们最好的模型是经过微调的模型,其中包含转移学习和图像增强功能,从而为我们提供了一个模型 准确度和 **96%**的 F1 得分,考虑到我们从 3,000 个图像训练数据集中训练了模型,这真是太了不起了。 现在,让我们绘制最差模型和最佳模型的 ROC 曲线: + +```py +# worst model - basic CNN +meu.plot_model_roc_curve(basic_cnn, test_imgs_scaled, + true_labels=test_labels_enc, class_names=[0, + 1]) + +# best model - transfer learning with fine-tuning & image augmentation meu.plot_model_roc_curve(tl_img_aug_finetune_cnn, test_imgs_scaled, + true_labels=test_labels_enc, class_names=[0, + 1]) +``` + +我们得到的图如下: + +![](img/8ccc305e-368b-48e0-a031-c076776e0c1e.png) + +这应该给您一个很好的主意,即预训练模型和迁移学习可以带来多大的差异,尤其是当我们面临诸如数据较少等约束时,在解决复杂问题上。 我们鼓励您使用自己的数据尝试类似的策略。 + +# 摘要 + +本章的目的是让您更深入地了解构建深度学习模型以解决实际问题,并了解迁移学习的有效性。 我们涵盖了迁移学习需求的各个方面,尤其是在解决数据受限的问题时。 我们从头开始构建了多个 CNN 模型,还看到了适当的图像增强策略的好处。 我们还研究了如何利用预训练的模型进行迁移学习,并介绍了使用它们的各种方法,包括用作特征提取器和微调。 我们看到了 VGG-16 模型的详细架构,以及如何利用该模型作为有效的图像特征提取器。 与转移学习有关的策略(包括特征提取和微调以及图像增强)被用来构建有效的深度学习图像分类器。 + +最后但并非最不重要的一点是,我们在测试数据集上评估了所有模型,并获得了一些卷积神经网络在构建特征图时如何在内部可视化图像的观点。 在随后的章节中,我们将研究需要转移学习的更复杂的实际案例研究。 敬请关注! \ No newline at end of file diff --git a/docs/handson-tl-py/6.md b/docs/handson-tl-py/6.md new file mode 100644 index 0000000000000000000000000000000000000000..30ca54344fb50609b7ae64eb3a002f7b59229e61 --- /dev/null +++ b/docs/handson-tl-py/6.md @@ -0,0 +1,462 @@ +# 图像识别与分类 + +知识投资永远是最大的利益。 + +- 本杰明·富兰克林 + +在计算机视觉的保护下,图像识别是一个活跃的跨学科研究领域。 顾名思义,图像或对象识别是识别图像或视频序列中的对象的任务。 传统上,该领域利用数学和计算机辅助建模以及对象设计方面的进步。 这些年来,已经开发了一些手工注释的数据集,以测试和评估图像识别系统。 我们现在称它们为传统技术,一直统治着整个场景,并且不断地改进这项任务,直到最近。 2012 年,深度学习参加了 ImageNet 竞赛,为快速改善和进步计算机视觉和深度学习技术打开了闸门。 + +在本章中,我们将从深度学习(尤其是转移学习)的角度介绍图像识别和分类的概念。 本章将涵盖以下方面: + +* 深度学习图像分类简介 +* 基准数据集 +* 最新的深度图像分类模型 +* 图像分类和转移学习用例 + +本章从本书的第三部分开始。 在本书的这一部分中,我们将涵盖涉及前两部分中讨论的概念和技术的案例研究。 这些用例将呈现现实世界的主题/研究领域,并帮助您了解如何在不同的环境中利用转移学习。 您可以在 GitHub 存储库中的 [https://github.com/dipanjanS/hands-on-transfer-learning-with-python](https://github.com/dipanjanS/hands-on-transfer-learning-with-python) 的 GitHub 存储库的`Chapter 6`文件夹中快速阅读本章的代码。 可以根据需要参考本章。 + +# 基于深度学习的图像分类 + +**卷积神经网络**( **CNN** )是这项深度学习革命的核心,旨在改善图像分类任务。 CNN 是处理图像数据的专用神经网络。 作为快速补充,CNN 可以通过它们共享的权重架构帮助我们推断出位移和空间不变特征,并且基本上是前馈网络的变体。 在[第 3 章](../Text/3.html),*了解深度学习架构*和[第 5 章](../Text/5.html),*中,我们已经详细介绍了 CNN 的基础知识。* 。 在继续进行之前,我们鼓励读者快速复习以更好地理解。 下图展示了运行中的典型 CNN: + +![](img/e6d21542-8a77-4a59-8dbe-58e16803c0be.png) + +典型的 CNN [来源:https://en.wikipedia.org/wiki/File:Typical_cnn.png] + +神经网络最早出现在 2011 年的图像分类竞赛中。受 GPU 训练的网络开始赢得竞赛。 在 2012 年,深层的 CNN 在 ImageNet 图像分类任务上的表现从以前的最好水平提高到 83%,这是世界上第一次注意到。 结果令人惊讶,足以引起全球关注,并有助于通过深度学习解决用例的泛滥。 + +# 基准数据集 + +图像分类,或就此而言,任何分类任务,本质上都是监督学习任务。 受监督的任务通过可用的基础培训集了解不同的类别。 + +即使 CNN 是共享权重的优化前馈网络,但要在深层 ConvNet 中训练的参数数量可能仍然很大。 这就是为什么需要大量的培训才能获得性能更好的网络的原因之一。 幸运的是,全球研究小组一直在努力收集,手工注释和众包不同的数据集。 这些数据集可用于对不同算法的性能进行基准测试,以及识别不同比赛中的获胜者。 + +以下是图像分类领域中广泛接受的基准数据集的简要列表: + +* **ImageNet** :这是一个具有黄金标准的可视数据集,具有超过 1400 万个带有手工注释的高分辨率彩色图像,涵盖了 20,000 个类别。 它是 2009 年由普林斯顿大学计算机科学系设计用于视觉对象识别任务的。此后,此数据集(在其 1000 个非重叠类的修整版本中)已用作 *ImageNet 的基础。 大规模视觉识别挑战*( [https://arxiv.org/abs/1409.0575](https://arxiv.org/abs/1409.0575) )。 +* **8000 万个微小图像数据集**:顾名思义,该 MIT 数据集包含从互联网收集的 8000 万个图像,并标记了 75,000 多个不同的非抽象英语名词。 该数据集还为其他广泛使用的数据集(包括 CIFAR 数据集)奠定了基础。 +* **CIFAR-10** :由加拿大高级研究所开发,CIFAR-10 是**机器学习**( **ML** )研究中使用最广泛的数据集之一。 该数据集包含 60,000 张横跨 10 个非重叠类的低分辨率图像。 +* **CIFAR-100** :来自同一研究组,该数据集包含 60,000 张图像,均匀分布在 100 个不同的类别中。 +* **上下文中的公共对象**:**上下文中的公共对象**( **COCO** )是用于对象标识,分段和字幕的大型可视数据库。 该数据集包含超过 200,000 张跨越不同类别的标记图像。 +* **打开的图像**:这可能是最大的可用注释数据集之一。 该数据集的版本 4 包含超过 900 万个带批注的图像。 +* **Caltech 101 和 Caltech 256** :这些数据集包含分别跨越 101 和 256 个类别的带注释的图像。 加州理工学院 101 包含约 9,000 张图像,而加州理工学院 256 包含近 30,000 张图像。 +* **斯坦福犬数据集**:这是一个有趣的数据集,特定于不同的犬种。 它包含 20,000 个彩色图像,涵盖 120 个不同的犬种。 +* **MNIST** :MNIST 是有史以来最著名的视觉数据集之一,已成为 ML 爱好者的事实上的 *Hello,World* 数据集。 它包含超过 60,000 个手工标记的数字(零到九个数字)。 + +上面的列表仅是冰山一角。 还有许多其他数据集可以捕获世界的不同方面。 准备这些数据集是一个痛苦且耗时的过程,但是这些数据集使深度学习在当前形式下如此成功。 鼓励读者详细研究这些和其他此类数据集,以了解它们背​​后的细微差别以及这些数据集为我们解决的挑战。 在本章和后续章节中,我们将利用其中的一些数据集来理解迁移学习的概念。 + +# 最新的深度图像分类模型 + +多年来,深度学习已引起了广泛的关注和炒作。 不足为奇的是,在全球范围内以深度学习为中心的知名竞赛,会议和期刊上共享了大量研究工作。 尤其是图像分类体系结构已经引起人们的关注,这几年来,定期进行迭代改进一直是人们关注的重点。 让我们快速了解一些性能最佳,最流行的最新深度图像分类体系结构: + +* **AlexNet** :这是可以归功于*打开闸门*的网络。 由深度学习的先驱之一 Geoffrey Hinton 和团队设计,该网络将前五名的错误率降低到了 15.3%。 它也是最早利用 GPU 加快学习过程的架构之一。 +* **VGG-16** :牛津大学视觉几何小组的网络是性能最好的体系结构之一,广泛用于对其他设计进行基准测试。 VGG-16 采用了一个简单的体系结构,该体系结构是基于 3 x 3 个卷积层(一个 16 层)相互堆叠,然后是一个最大池化层,以实现强大的性能。 该模型由稍微更复杂的模型 **VGG19** 继承。 +* **初始**:也称为 **GoogleNet** ,该网络是在 **ImageNet 大规模视觉识别挑战赛**( **ILSVRC** )在 2014 年实现了前五名的错误率为 6.67%。 它是最早实现接近人类性能的体系结构之一。 该网络背后的新颖之处在于使用了起始层,该起始层包括了在同一级别将不同大小的内核连接在一起的过程。 +* **ResNet** :由 Microsoft Research Asia 引入,**残留网络**( **ResNet** )是一种新颖的体系结构,利用批处理规范化和跳过连接来实现前五位的错误率 只有 3.57%。 它比诸如 VGG 之类的简单体系结构要深很多(152 层)并且要复杂得多。 +* **MobileNet** :尽管大多数架构都在竞争中胜过其他架构,但每个新的复杂网络都需要更多的计算能力和数据资源。 MobileNet 偏离了此类架构,并被设计为适用于移动和嵌入式系统。 该网络利用了一种新颖的思想,即使用深度可分离卷积来减少训练网络所需的参数总数。 + +我们提供了基于深度学习的图像分类空间中一些最新架构的快速概述和概述。 有关详细讨论,读者可以查看[第 3 章](../Text/3.html),*了解深度学习架构*中的*卷积神经网络*部分。 + +# 图像分类与转移学习 + +到目前为止,我们已经讨论了什么是图像分类。 在本节中,我们将通过构建自己的分类器来弄清手。 在本章的较早部分中,我们简要提到了著名的基准测试数据集,包括 CIFAR-10 和 Stanford Dogs 数据集,我们将在接下来的部分中重点介绍这些数据集。 我们还将利用预先训练的模型来了解我们如何利用转移学习来改进我们的模型。 + +# CIFAR-10 + +**CIFAR-10** 是深度学习领域中使用最广泛的图像数据集之一。 由加拿大高级研究所准备,这是一个相当不错的数据集。 该数据集的主要优点是它包含 10 个非重叠类别的平衡分布。 图像的分辨率和尺寸较低,因此可以将数据集用于在较小的内存占用量系统上进行训练。 + +# 建立图像分类器 + +CIFAR-10 是少数可用的平衡数据集之一。 它的整体大小为 60,000 张图像。 以下代码段加载 CIFAR-10 数据集,并设置训练变量和测试变量: + +```py +# load CIFAR dataset +(X_train, y_train), (X_test, y_test) = cifar10.load_data() +``` + +数据集中的图像分辨率很低,有时甚至很难贴上标签。 IPython Notebook `CIFAR-10_CNN_Classifier.ipynb`中提供了本节中共享的代码。 + +我们已经讨论了 CNN 以及如何针对视觉数据集对其进行优化。 CNN 遵循权重分配原则以减少参数数量; 从头开始开发它们不仅需要强大的深度学习技能,还需要巨大的基础架构需求。 牢记这一点,从头开始开发 CNN 并测试我们的技能将很有趣。 + +以下代码片段展示了使用 Keras 构建的非常简单的 CNN,它只有五层(两层卷积,一层最大池化,一层密集和一层最终 softmax 层): + +```py +model = Sequential() +model.add(Conv2D(16, kernel_size=(3, 3), + activation='relu', +input_shape=INPUT_SHAPE)) + +model.add(Conv2D(32, (3,3), padding='same', +kernel_regularizer=regularizers.l2(WEIGHT_DECAY), + activation='relu')) +model.add(BatchNormalization()) +model.add(MaxPooling2D(pool_size=(2,2))) +model.add(Dropout(0.2)) + +model.add(Flatten()) +model.add(Dense(128, activation='relu')) +model.add(Dropout(0.5)) +model.add(Dense(NUM_CLASSES, activation='softmax')) +``` + +为了提高总体泛化性能,该模型还包含`BatchNormalization`层和`DropOut`层。 这些层有助于我们控制过度拟合,还可以防止网络存储数据集本身。 + +我们仅用 25 个纪元来运行模型,以在验证集上达到约 65%的准确性。 以下屏幕快照显示了训练后的模型的输出预测: + +![](img/80bb52c2-ee64-4f17-9739-0102fa0a8713.png) + +来自基于 CNN 的 CIFAR-10 分类器的预测 + +尽管绝不是最先进的结果,但结果足够不错。 读者应该记住,这个 CNN 只是为了展示手头的巨大潜力,我们鼓励您尝试在同一行上进行实验。 + +# 传播知识 + +由于本章和书着重于转移学习,因此让我们快速进行利用和转移所学信息的实际任务。 在上一节中,我们讨论了不同的最新 CNN 架构。 现在,让我们利用在 ImageNet 上训练的 VGG-16 模型对 CIFAR-10 数据集中的图像进行分类。 该部分的代码在 IPython Notebook `CIFAR10_VGG16_Transfer_Learning_Classifier.ipynb`中可用。 + +ImageNet 是一个庞大的视觉数据集,具有 20,000 多个不同类别。 另一方面,CIFAR-10 仅限于 10 个非重叠类别。 像 VGG-16 这样的强大网络需要巨大的计算能力和时间来训练,以达到比人类更好的表现。 这将转移学习带入了画面。 由于我们大多数人都无法访问无限的计算,因此我们可以在两种不同的设置下利用这些网络: + +* 使用经过预训练的最新网络作为特征提取器。 这是通过删除顶部分类层并使用倒数第二层的输出来完成的。 +* 在新数据集上微调最新的网络。 + +我们将利用 VGG-16 作为特征提取器,并在其之上构建自定义分类器。 以下代码段加载并准备了 CIFAR-10 数据集以供使用: + +```py +# extract data +(X_train, y_train), (X_test, y_test) = cifar10.load_data() + +#split train into train and validation sets +X_train, X_val, y_train, y_val = train_test_split(X_train, + y_train, + test_size=0.15, + stratify=np.array + (y_train), + random_state=42) + +# perform one hot encoding +Y_train = np_utils.to_categorical(y_train, NUM_CLASSES) +Y_val = np_utils.to_categorical(y_val, NUM_CLASSES) +Y_test = np_utils.to_categorical(y_test, NUM_CLASSES) + +# Scale up images to 48x48 +X_train = np.array([sp.misc.imresize(x, + (48, 48)) for x in X_train]) +X_val = np.array([sp.misc.imresize(x, + (48, 48)) for x in X_val]) +X_test = np.array([sp.misc.imresize(x, + (48, 48)) for x in X_test]) +``` + +前面的代码片段不仅将训练数据集拆分为训练集和验证集,还将目标变量转换为单热编码形式。 我们还将图像尺寸从 32 x 32 调整为 48 x 48,以符合 VGG-16 输入要求。 训练,验证和测试数据集准备就绪后,我们可以朝着准备分类器的方向努力。 + +以下代码段显示了我们如何轻松地在现有模型之上附加新层。 因为我们的目标是仅训练分类层,所以通过将可训练参数设置为`False`冻结其余层。 这使我们即使在功能较弱的基础架构上也可以利用现有架构,并将学到的权重从一个域转移到另一个域: + +```py +base_model = vgg.VGG16(weights='imagenet', +include_top=False, +input_shape=(48, 48, 3)) + +# Extract the last layer from third block of vgg16 model +last = base_model.get_layer('block3_pool').output + +# Add classification layers on top of it +x = GlobalAveragePooling2D()(last) +x= BatchNormalization()(x) +x = Dense(64, activation='relu')(x) +x = Dense(64, activation='relu')(x) +x = Dropout(0.6)(x) +pred = Dense(NUM_CLASSES, activation='softmax')(x) +model = Model(base_model.input, pred) + +for layer in base_model.layers: +layer.trainable = False +``` + +我们有基本的成分。 整个流水线中剩下的最后一块是数据扩充。 整个数据集仅包含 60,000 张图像; 数据扩充非常方便,可以为手头的样本集添加某些变化。 这些变体使网络能够学习比其他方法更通用的功能。 以下代码段利用`ImageDataGenerator()`实用工具准备训练和验证增强对象: + +```py +# prepare data augmentation configuration +train_datagen = ImageDataGenerator(rescale=1\. / 255, + horizontal_flip=False) + +train_datagen.fit(X_train) +train_generator = train_datagen.flow(X_train, + Y_train, + batch_size=BATCH_SIZE) + +val_datagen = ImageDataGenerator(rescale=1\. / 255, + horizontal_flip=False) + +val_datagen.fit(X_val) +val_generator = val_datagen.flow(X_val, + Y_val, + batch_size=BATCH_SIZE) +``` + +现在让我们训练模型几个时期并衡量其性能。 以下代码段调用`fit_generator()`函数将新添加的层训练到模型中: + +```py +train_steps_per_epoch = X_train.shape[0] // BATCH_SIZE +val_steps_per_epoch = X_val.shape[0] // BATCH_SIZE + +history = model.fit_generator(train_generator, + steps_per_epoch=train_steps_per_epoch, + validation_data=val_generator, + validation_steps=val_steps_per_epoch, + epochs=EPOCHS, + verbose=1) + +``` + +`fit_generator()`返回的历史对象包含有关每个时期的详细信息。 我们利用这些来绘制模型在精度和损失方面的整体性能。 结果如下所示: + +![](img/db41fbac-3130-4690-af32-deff0262e31d.png) + +火车验证性能 + +如我们所见,与从头开始开发的模型相比,迁移学习帮助我们在整体性能上实现了惊人的提升。 这项改进利用了训练有素的 VGG-16 权重将学习到的功能转移到该域。 读者可以使用相同的实用程序`plot_predictions()`可视化随机样本上的分类结果,如以下屏幕截图所示: + +![](img/fb2a9e1f-dc9f-47c7-9e67-26b712c100ab.png) + +使用基于 VGG-16 的迁移学习构建的分类器的预测结果 + +神经网络是相当复杂的学习机器,很难调试和优化。 尽管有许多可用的技术,但需要经验来微调网络。 在当前情况下,使用像 VGG-16 这样的深层 CNN 可能对如此小尺寸的图像来说是过大的杀伤力,但它显示了巨大的潜力。 明智地使用它! + +这是迁移学习的快速简单的应用,其中我们利用了像 VGG-16 这样令人惊讶的复杂深度 CNN 来准备 CIFAR-10 分类器。 鼓励读者不仅尝试定制分类器的不同配置,而且尝试甚至使用不同的预训练网络来理解所涉及的复杂性。 + +# 狗品种鉴定数据集 + +在上一节中,我们利用低分辨率图像数据集将图像分类为 10 个非重叠类别。 这绝非易事,但我们以最小的努力获得了不错的性能。 + +现在让我们升级游戏,并使图像分类的任务更加令人兴奋。 在本节中,我们将专注于细粒度图像分类的任务。 与常规图像分类任务不同,细粒度图像分类是指识别更高级别类别中不同子类别的任务。 + +为了更好地理解此任务,我们将围绕**斯坦福犬**数据集( [http://vision.stanford.edu/aditya86/ImageNetDogs/](http://vision.stanford.edu/aditya86/ImageNetDogs/) )进行讨论。 顾名思义,该数据集包含不同犬种的图像。 在这种情况下,任务是识别每种犬种。 因此,高级概念是狗本身,而任务是正确分类不同的子概念或子类(在这种情况下为品种)。 该数据集包含来自 ImageNet 数据集的 20,000 个带有标签的图像,该图像集由 120 个不同的犬种组成。 为了便于讨论,我们将利用 **Kaggle** 提供的数据集。 该数据集位于以下链接中: [https://www.kaggle.com/c/dog-breed-identification/data](https://www.kaggle.com/c/dog-breed-identification/data) 。 + +让我们开始构建狗分类器的任务。 但是,在实际模型之前,让我们对数据集本身进行快速探索性分析,以更好地理解。 + +# 探索性分析 + +我们不能足够强调理解底层数据集的重要性。 在当前情况下,我们正在处理一个视觉数据集,该数据集由 10,000 个样本组成,分布在 120 个类别(狗的品种)中。 读者可以在名为`dog_breed_eda.ipynb`的 IPython Notebook 中参考与探索性分析有关的所有步骤。 + +由于这是一个可视数据集,因此我们首先将数据集中的一些样本可视化。 有多种方法可以在 Python 中提取和可视化图像数据。 我们将依靠 SciPy 和与 matplotlib 相关的实用程序来做到这一点。 以下代码片段导入所需的库: + +```py +In [1]: import os + ...: import scipy as sp + ...: import numpy as np + ...: import pandas as pd + ...: + ...: import PIL + ...: import scipy.ndimage as spi + ...: + ...: import matplotlib.pyplot as plt + ...: import seaborn as sns + ...: + ...:np.random.seed(42) +``` + +由于数据集很大,因此我们准备了几个实用程序来加载随机的图像批处理并显示选定的批处理。 实用程序的标题为`load_batch()`和`plot_batch()`; 这些细节在 IPython Notebook 中可用。 以下代码段绘制了随机批次以供参考: + +```py +In [7]:batch_df = load_batch(dataset_df, + ...: batch_size=36) + +In [8]:plot_batch(batch_df, grid_width=6, grid_height=6 + ...: ,im_scale_x=64, im_scale_y=64) +``` + +生成的输出如下网格所示: + +![](img/b2f44b73-b96f-423b-ae01-abc016a16f8a.png) + +狗品种识别数据集的样本图像 + +从前面的网格中,我们可以看到在分辨率,照明,缩放级别等方面存在很多变化,并且图像不仅包含一只狗,而且还包含其他狗和周围环境。 项目。 我们还需要了解图像尺寸的差异。 使用以下代码段,我们生成了一个散点图来理解它们: + +```py + In [12]: plt.plot(file_dimension_list[:, 0], + file_dimension_list[:, 1], "ro") + ...: plt.title("Image sizes") + ...: plt.xlabel("width") + ...: plt.ylabel("height") +``` + +生成的散点图如下所示。 我们可以清楚地看到最大图像数量在 500 x 500 尺寸之内,但是形状确实有所不同: + +![](img/3fadb37c-895c-49fa-b6ad-3239d4c43071.png) + +图像大小的散点图 + +还需要检查狗的品种分布,以了解我们正在处理什么。 由于我们具有标记的数据集,因此我们可以轻松地进行检查。 以下代码段使用`pandas`绘制品种分布: + +```py + In [13]: fig = plt.figure(figsize = (12,5)) + ...: + ...: ax1 = fig.add_subplot(1,2, 1) + ...:dataset_df.breed.value_counts().tail().plot('bar', + ...: ax=ax1,color='gray', + ...: title="Breeds with Lowest Counts") + ...: + ...: ax2 = fig.add_subplot(1,2, 2) + ...:dataset_df.breed.value_counts().head().plot('bar', + ...: ax=ax2,color='black', + ...: title="Breeds with Highest Counts") +``` + +数据集没有被平均分割; 与其他品种相比,某些品种的样本更多。 从下图中的图可以明显看出: + +![](img/9973f689-d94a-49b1-97ec-90e66790785c.png) + +最高和最低犬种样本数 + +这样的数据集需要彻底的探索。 我们已经在本节中介绍了一些探索性步骤。 进一步的步骤在引用的 IPython Notebook 中列出/执行。 鼓励读者逐步了解图像大小调整的影响,不同图层如何检测不同特征,灰度等。 + +# 资料准备 + +探索性分析有助于我们更好地了解手头的数据集。 下一个任务是为数据集构建一个实际的分类器。 众所周知,对于任何分类问题,第一步都是将数据集分为训练集和验证集。 由于我们正在使用 Keras,因此我们将从其实用程序中获取帮助以帮助准备我们的数据集。 以下代码段展示了将原始数据集组织为训练集和验证集的过程: + +```py +# Prepare column to store image path +data_labels['image_path'] = data_labels.apply( + lambda row: (train_folder + + row["id"] + ".jpg" ), + axis=1) +# load image data as arrays of defined size +train_data = np.array([img_to_array(load_img(img, target_size=(299, + 299))) + for img in data_labels['image_path'].values.tolist() + ]).astype('float32') + +# split data into train and test +x_train, x_test, y_train, y_test = train_test_split(train_data, + target_labels, + test_size=0.3, + stratify=np.array(target_labels), + random_state=42) + +# split train dataset into train and validation sets +x_train, x_val, y_train, y_val = train_test_split(x_train, + y_train, + test_size=0.15, + stratify= + np.array(y_train), + random_state=42) + +``` + +前面显示的第一步是准备标签`dataframe`中的派生列以保存实际图像路径。 然后,我们继续简单地将数据集分为训练,验证和测试数据集。 下一步是在将标签输入模型之前,将标签快速转换为一次性编码形式。 以下代码段准备了目标变量的一键编码形式: + +```py +y_train_ohe = pd.get_dummies(y_train.reset_index( + drop=True) + ).as_matrix() +y_val_ohe = pd.get_dummies(y_val.reset_index( + drop=True) + ).as_matrix() +y_test_ohe = pd.get_dummies(y_test.reset_index( + drop=True) + ).as_matrix() +``` + +众所周知,深度学习算法需要大量数据。 在这种情况下,即使我们总共有 10,000 张图像,每个类别的计数也不是很大。 为了对此进行改进,我们执行增强。 简单来说,**数据增强**是利用策展的数据集通过生成现有数据点的变体来扩大自身的过程。 在这种情况下,我们利用`keras`中的`ImageDataGenerator`来扩充训练和验证数据集,如以下代码片段所示: + +```py +# Create train generator. +train_datagen = ImageDataGenerator(rescale=1./255, + rotation_range=30, + width_shift_range=0.2, + height_shift_range=0.2, + horizontal_flip = 'true') + +train_generator = train_datagen.flow(x_train, + y_train_ohe, + shuffle=False, + batch_size=BATCH_SIZE, + seed=1) + +# Prepare Validation data augmentation +val_datagen = ImageDataGenerator(rescale = 1./255) +val_generator = train_datagen.flow(x_val, + y_val_ohe, + shuffle=False, + batch_size=BATCH_SIZE, + seed=1) +``` + +现在我们已经准备好数据,下一步是准备实际的分类器。 + +# 使用转移学习的狗分类器 + +现在我们的数据集已经准备好,让我们开始建模过程。 我们已经知道如何从头开始构建深度的卷积网络。 我们也了解达到良好性能所需的微调数量。 对于此任务,我们将利用转移学习的概念。 + +预训练模型是开始进行迁移学习任务所需的基本要素。 如前几章所述,可以通过在当前任务上微调预训练网络的权重,或将预训练模型用作特征提取器,来利用转移学习。 + +在这种用例中,我们将集中精力利用预训练的模型作为特征提取器。 众所周知,深度学习模型基本上是神经元相互连接的层的堆叠,最后一层充当分类器。 这种架构使深度神经网络能够捕获网络中不同级别的不同特征。 因此,我们可以利用此属性将它们用作特征提取器。 通过删除最后一层或使用倒数第二层的输出,可以做到这一点。 然后,将倒数第二层的输出馈送到其他一组层,然后是分类层。 以下代码段展示了基于 **InceptionV3** 预训练模型的特征提取,并堆叠了其他层以准备分类器: + +```py +# Get the InceptionV3 model so we can do transfer learning +base_inception = InceptionV3(weights='imagenet', + include_top = False, + input_shape=(299, 299, 3)) + +# Add a global spatial average pooling layer +out = base_inception.output +out = GlobalAveragePooling2D()(out) +out = Dense(512, activation='relu')(out) +out = Dense(512, activation='relu')(out) +total_classes = y_train_ohe.shape[1] + +predictions = Dense(total_classes, + activation='softmax')(out) +``` + +如前面的代码片段所示, **Keras** 提供了用于处理许多预训练模型的简单实用程序,将它们用作特征提取器就像将标志`include_top`设置为`False`一样简单。 在下面的代码片段中,我们通过将两组图层堆叠在一起,然后冻结 InceptionV3 中的图层来准备最终模型: + +```py +model = Model(inputs=base_inception.input, + outputs=predictions) + +# only if we want to freeze layers +for layer in base_inception.layers: + layer.trainable = False +``` + +现在,我们有了模型,所有模型都将在“狗品种识别”数据集中进行训练。 我们使用`fit_generator()`方法训练模型,以利用上一步中准备的数据增强。 我们将批次大小设置为 32,并训练模型 13 个纪元。 以下代码片段设定了滚动的方向: + +```py +batch_size = BATCH_SIZE +train_steps_per_epoch = x_train.shape[0] // batch_size +val_steps_per_epoch = x_val.shape[0] // batch_size + +history = model.fit_generator(train_generator, + steps_per_epoch=train_steps_per_epoch, + validation_data=val_generator, + validation_steps=val_steps_per_epoch, + epochs=15, + verbose=1) +``` + +由于我们在每个时期(`history`对象)之后都保存了模型参数和性能的输出,因此我们现在将利用它来了解模型性能。 下图绘制了模型的训练和测试精度以及其损失性能: + +![](img/499881f7-60ac-48f4-acdc-9b0b63f1ec1d.png) + +狗品种分类器性能 + +该模型在训练集和验证集上仅在 15 个时期内就获得了 **80%**精度以上的可嘉性能。 右侧的图显示了损耗下降并收敛到 **0.5** 的速度。 这是一个很好的例子,说明了转移学习的强大而简单。 + +训练和验证的性能相当不错,但是对看不见的数据的性能如何? 由于我们已经将原始数据集分为三个单独的部分。 这里要记住的重要一点是,测试数据集必须经过与训练数据集相似的预处理。 为了解决这个问题,在将测试数据集输入到函数之前,我们还对它进行了缩放。 + +该模型在测试数据集上实现了惊人的 **85%准确度**以及 **0.85 F1 得分**。 鉴于我们仅用最少的投入就训练了 15 个纪元,转移学习帮助我们实现了一个不错的分类器: + +![](img/37a13513-298a-414a-beeb-76c68f9c1a36.png) + +狗的品种分类器预测 + +上图显示了模型性能的视觉证明。 正如我们所看到的,在大多数情况下,该模型不仅可以预测正确的犬种,而且还可以非常有把握地进行预测。 + +# 摘要 + +在本书的前两个部分中,我们讨论了很多理论。 建立了强大的概念和技术基础后,我们在本章中开始了用例驱动的旅程。 本章是一系列后续章节中的第一章,这些章节将展示不同场景和领域中转移学习的实际用例。 在本章中,我们将转移学习应用于视觉对象识别或俗称**图像分类**的领域。 + +我们从围绕 CNN 的快速更新开始,并随着 2012 年深度学习模型的出现,计算机辅助对象识别的整个过程将一劳永逸。我们简要介绍了各种最新的图像分类模型 ,它已经超越了人类的表现。 我们还快速浏览了学术界和行业专家用于培训和调整模型的不同基准数据集。 设置上下文后,我们从 CIFAR-10 数据集开始。 我们使用 Keras 和 TensorFlow 作为后端从头开始构建了一个分类器。 我们利用 VGG-16 作为特征提取的预训练模型,利用转移学习来改善此性能。 + +在本章的最后部分,我们利用转移学习来解决稍微复杂的问题。 我们准备了一个犬种分类器,而不是基于类别不重叠的数据集(CIFAR-10),以基于 Stanford Dogs 数据集识别 120 种不同的犬种。 只需几行代码,我们就可以实现近乎最新的性能。 第二种用例也称为*细粒度图像分类*任务,并且比通常的图像分类任务复杂。 在本章中,我们展示了通过迁移学习获得惊人结果的强大功能和简便性。 在接下来的章节中,请继续关注来自计算机视觉,音频分析等领域的惊人用例。 \ No newline at end of file diff --git a/docs/handson-tl-py/7.md b/docs/handson-tl-py/7.md new file mode 100644 index 0000000000000000000000000000000000000000..586ac28117941bef8a66c88a01ad9cab3a62665d --- /dev/null +++ b/docs/handson-tl-py/7.md @@ -0,0 +1,1036 @@ +# 文字文件分类 + +在本章中,我们讨论了转移学习在文本文档分类中的应用。 文本分类是一种非常流行的自然语言处理任务。 关键目标是根据文档的文本内容将文档分配到一个或多个类别或类别。 这在行业中得到了广泛的应用,包括将电子邮件分类为垃圾邮件/非垃圾邮件,审阅和评级分类,情感分析,电子邮件或事件路由,在此我们将电子邮件\事件分类,以便可以将其自动分配给相应的人员。 以下是本章将涉及的主要主题: + +* 文本分类概述,行业应用和挑战 +* 基准文本分类数据集和传统模型的性能 +* 密集向量的单词表示—深度学习模型 +* CNN 文档模型-单词到句子的嵌入,然后进行文档嵌入 +* 源和目标域分布不同的转移学习的应用; 也就是说,源域由重叠较少的类组成,目标域具有许多混合类 +* 源和目标域本身不同的转移学习的应用(例如,源是新闻,目标是电影评论,依此类推) +* 训练有素的模型在完成其他文本分析任务(例如文档摘要)中的应用-解释为什么将评论归类为负面/正面 + +我们将通过动手示例来关注概念和实际实现。 您可以在 GitHub 存储库中的 [https://github.com/dipanjanS/hands-on-transfer-learning-with-python](https://github.com/dipanjanS/hands-on-transfer-learning-with-python) 的 GitHub 存储库的`Chapter 7`文件夹中快速阅读本章的代码。 可以根据需要参考本章。 + +# 文字分类 + +给定一组文本文档和一组预定义类别,文本分类的目的是将每个文档分配给一个类别。 根据问题,输出可以是软分配或硬分配。 软分配意味着将类别分配定义为所有类别上的概率分布。 + +文本分类在工业中有广泛的应用。 以下是一些示例: + +* **垃圾邮件过滤**:给定电子邮件,将其分类为垃圾邮件或合法电子邮件。 +* **情感分类**:给定评论文字(电影评论,产品评论),请确定用户的极性-无论是正面评论,负面评论还是神经评论。 +* **问题单分配**:通常,在任何行业中,每当用户遇到有关任何 IT 应用程序或软件/硬件产品的问题时,第一步就是创建问题单。 这些票证是描述用户所面临问题的文本文档。 下一个合乎逻辑的步骤是,某人必须阅读说明并将其分配给具有适当专业知识的团队才能解决问题。 现在,给定一些历史故障单和解决方案团队类别,可以构建文本分类器以自动对问题故障单进行分类。 +* **问题单的自动解决方案**:在某些情况下,问题的解决方案也是预先定义的; 也就是说,专家团队知道解决该问题应遵循的步骤。 因此,在这种情况下,如果可以高精度地构建文本分类器来对票证进行分类,则一旦预测了票证类别,便可以运行自动脚本来直接解决问题。 这是未来 **IT 运营人工智能**( **AIOps** )的目标之一。 +* **有针对性的营销**:营销人员可以监视社交媒体中的用户,并将其分类为促进者或破坏者,并基于此,对在线产品发表评论。 +* **体裁分类**:自动文本体裁分类对于分类和检索非常重要。 即使一组文档属于同一类别,因为它们共享一个共同的主题,但它们通常具有不同的用途,属于不同的流派类别。 如果可以检测到搜索数据库中每个文档的类型,则可以根据用户的喜好更好地向用户呈现信息检索结果。 +* **索赔中的欺诈检测**:分析保险索赔文本文档并检测索赔是否为欺诈。 + +# 传统文字分类 + +构建文本分类算法/模型涉及一组预处理步骤以及将文本数据正确表示为数值向量。 以下是一般的预处理步骤: + +1. **句子拆分**:将文档拆分为一组句子。 +2. **标记化**:将句子拆分为组成词。 +3. **词干或词根去除**:单词标记被简化为它们的基本形式。 例如,诸如演奏,演奏和演奏之类的单词具有一个基数:*演奏*。 词干的基本单词输出不必是词典中的单词。 而来自残词化的根词,也称为**引理**,将始终存在于字典中。 + +4. **文本清除**:大小写转换,更正拼写并删除停用词和其他不必要的术语。 + +给定文本文档的语料库,我们可以应用前面的步骤,然后获得构成语料库的单词的纯净词汇。 下一步是文本表示。 **b** **单词**( **BoW** )模型是从文本文档中提取特征并创建文本向量表示的最简单但功能最强大的技术之一。 如果我们在提取的词汇表中有 *N* 个单词,则任何文档都可以表示为 *D = {w 1 ,w 2 ,...。 。 ,其中 *w i* 代表文档中单词出现的频率。 这种文本作为稀疏向量的表示称为 BoW 模型。 在这里,我们不考虑文本数据的顺序性质。 一种部分捕获顺序信息的方法是在构建词汇表时考虑单词短语或 n-gram 和单个单词特征。 但是,挑战之一是我们的代表人数。 也就是说,我们的词汇量爆炸了。* + +文档向量也可以表示为二进制向量,其中每个 *w i ∈{0,1}* 表示文档中单词的存在或不存在。 最受欢迎的表示形式是单词频率的归一化表示形式,称为**词频-文档频率**( **TF-IDF** )表示形式。 通过将我们语料库中的文档总数除以每个术语的文档频率,然后对结果应用对数缩放,可以计算出 IDF 表示的文档逆频率。 TF-IDF 值是*词频*与*倒排文档频率*的乘积。 它与单词在文档中出现的次数成正比地增加,并根据语料库中单词的频率按比例缩小,这有助于调整某些单词通常更频繁出现的事实。 + +现在,我们都准备建立一个分类模型。 我们需要一套带有标签的文件或培训数据。 以下是一些流行的文本分类算法: + +* 多项式朴素贝叶斯 +* 支持向量机 +* k 最近邻居 + +具有线性核的**支持向量机**( **SVM** )与用于文本分类的基准数据集相比,通常显示出更高的准确性。 + +# BoW 模型的缺点 + +使用基于单词计数的 BoW 模型,我们将丢失其他信息,例如每个文本文档中附近单词周围的语义,结构,序列和上下文。 在 BoW 中,具有相似含义的单词将得到不同的对待。 其他文本模型是**潜在语义索引**( **LSI** ),其中文档以低维度(k < <词汇量)-隐藏的主题空间表示。 在 LSI 中,文档中的组成词也可以表示为 *k* 维密集向量。 据观察,在 LSI 模型中,具有相似语义的单词具有紧密的表示形式。 而且,单词的这种密集表示是将深度学习模型应用于文本的第一步,被称为**单词嵌入**。 基于神经网络的语言模型试图通过查看语料库中的单词序列来预测其相邻单词的单词,并在此过程中学习分布式表示,从而使我们能够密集地嵌入单词。 + +# 基准数据集 + +以下是大多数文本分类研究中使用的基准数据集的列表: + +* **IMDB 电影评论数据集**:这是用于二进制情感分类的数据集。 它包含一组用于培训的 25,000 条电影评论和用于测试的 25,000 条电影。 也有其他未标记的数据可供使用。 该数据集可从 [http://ai.stanford.edu/~amaas/data/sentiment/](http://ai.stanford.edu/~amaas/data/sentiment/) 下载。 +* **路透数据集**:此数据集包含 90 个类别,9,584 个培训文档和 3,744 个测试文档。 它是软件包`nltk.corpus`的一部分。 该数据集中文档的类分布非常不正确,其中两个最常见的类包含大约所有文档的 70%。 即使仅考虑 10 个最频繁的类,该数据集中的两个最频繁的类也拥有大约 80%的文档。 因此,大多数分类结果都是在这些最常见的班级的子集上进行评估的,它们在火车集中的最常见的 8、10 和 52 班级分别命名为 R8,R10 和 R52。 +* **20 个新闻组数据集**:此数据被组织成 20 个不同的新闻组,每个新闻组对应一个不同的主题。 一些新闻组彼此之间有着非常密切的关联(例如:`comp.sys.ibm.pc.hardware` / `comp.sys.mac.hardware`),而其他新闻组则是高度不相关的(例如:`misc.forsale` / `soc.religion.christian`)。 这是 20 个新闻组的列表,根据主题分为六个主要类别。 该数据集在`sklearn.datasets`中可用: + +| `comp.graphics``comp.os.ms-windows.misc``comp.sys.ibm.pc.hardware``comp.sys.mac.hardware``comp.windows.x` | `rec.autos``rec.motorcycles``rec.sport.baseball``rec.sport.hockey` | `sci.crypt``sci.electronics``sci.med``sci.space` | +| `misc.forsale` | `talk.politics.misc``talk.politics.guns``talk.politics.mideast` | `talk.religion.misc``alt.atheism``soc.religion.christian` | + +稍后我们将讨论如何加载此数据集以进行进一步分析。 + +# 单词表示 + +让我们看一下这些用于处理文本数据并从中提取有意义的特征或单词嵌入的高级策略,这些策略可用于其他**机器学习**( **ML** )系统中,以执行更高级的任务 例如分类,摘要和翻译。 我们可以将学习到的单词表示形式转移到另一个模型中。 如果我们拥有大量的培训数据,则可以与最终任务一起共同学习单词嵌入。 + +# Word2vec 模型 + +该模型由 Google 于 2013 年创建,是一种基于深度学习的预测模型,该模型可计算并生成高质量,分布式和连续密集的单词向量表示,从而捕获上下文和语义相似性。 从本质上讲,这些是无监督模型,可以吸收大量文本语料库,创建可能单词的词汇表,并为代表该词汇表的向量空间中的每个单词生成密集的单词嵌入。 通常,您可以指定单词嵌入向量的大小,向量的总数本质上是词汇表的大小。 这使得该密集矢量空间的维数大大低于使用传统 BoW 模型构建的高维稀疏矢量空间。 + +Word2vec 可以利用两种不同的模型架构来创建这些词嵌入表示。 这些是: + +* **连续单词袋**( **CBOW** )模型 +* **跳过图**模型 + +CBOW 模型体系结构尝试根据源上下文单词(环绕单词)来预测当前的目标单词(中心单词)。 考虑一个简单的句子*,棕色狐狸跳过了懒狗*,这可以是一对[`context_window`和`target_word`),如果我们考虑一个大小为 2 的上下文窗口,我们有一些例子 像*([快速,狐狸],棕色)*,*([the,棕色],快速)*,*([the,狗],懒惰)*,,依此类推。 因此,该模型尝试根据上下文窗口词来预测目标词。 Word2vec 系列模型是不受监管的; 这意味着您可以给它一个语料库,而无需附加标签或信息,并且它可以从语料库构建密集的单词嵌入。 但是,一旦有了这个语料库,您仍然需要利用监督分类方法。 但是我们将在没有任何辅助信息的情况下从语料库内部进行操作。 我们可以将此 CBOW 体系结构建模为深度学习分类模型,以便将上下文词作为输入 *X* ,并尝试预测目标词 *Y* 。 实际上,构建这种体系结构比跳过语法模型更简单,在该模型中,我们尝试从源目标词预测一大堆上下文词。 + +跳过语法模型体系结构通常尝试实现与 CBOW 模型相反的功能。 它尝试在给定目标词(中心词)的情况下预测源上下文词(环绕词)。 考虑一下前面的简单句子*,快速的棕色狐狸跳过了懒狗*。 如果我们使用 CBOW 模型,则会得到(`context_window`和`target_word`)对,其中,如果考虑大小为 2 的上下文窗口,则有类似*([quick,fox],brown)*的示例 ],*([the,棕色],快速)*,*([the,狗],懒惰)*等。 现在,考虑到跳过语法模型的目的是根据目标单词预测上下文,该模型通常会反转上下文和目标,并尝试根据其目标单词预测每个上下文单词。 + +Hence, the task becomes to predict the context [*quick*, *fox*] given the target word *brown* or [*the*, *brown*] given the target word *quick*, and so on. Thus, the model tries to predict the `context_window` words based on the `target_word`. + +以下是前两个模型的体系结构图: + +![](img/03d09bc9-6d58-44b0-ae56-9fca79a2c304.png) + +我们中的一个人可以在以下博客文章中找到这些模型在 Keras 中的实现: [https://towardsdatascience.com/understanding-feature-engineering-part-4-deep-learning-methods-for-text -data-96c44370bbfa](https://towardsdatascience.com/understanding-feature-engineering-part-4-deep-learning-methods-for-text-data-96c44370bbfa) 。 + +# 使用 Gensim 的 Word2vec + +Radim Rehurek 创建的 gensim 框架由 Word2vec 模型( [https://radimrehurek.com/gensim/models/word2vec.html](https://radimrehurek.com/gensim/models/word2vec.html) )的可靠,高效且可扩展的实现组成。 它使我们可以选择跳跃语法模型或 CBOW 模型之一。 让我们尝试学习和可视化 IMDB 语料库的词嵌入。 如前所述,它有 50,000 个带标签的文档和 50,000 个无标签的文档。 对于学习单词表示,我们不需要任何标签,因此可以使用所有可用的 100,000 个文档。 + +首先加载完整的语料库。 下载的文档分为`train`,`test`和`unsup`文件夹: + +```py +def load_imdb_data(directory = 'train', datafile = None): + ''' + Parse IMDB review data sets from Dataset from + http://ai.stanford.edu/~amaas/data/sentiment/ + and save to csv. + ''' + labels = {'pos': 1, 'neg': 0} + df = pd.DataFrame() + + for sentiment in ('pos', 'neg'): + path =r'{}/{}/{}'.format(config.IMDB_DATA, directory, + sentiment) + for review_file in os.listdir(path): + with open(os.path.join(path, review_file), 'r', + encoding= 'utf-8') as input_file: + review = input_file.read() + df = df.append([[utils.strip_html_tags(review), + labels[sentiment]]], + ignore_index=True) + + df.columns = ['review', 'sentiment'] + indices = df.index.tolist() + np.random.shuffle(indices) + indices = np.array(indices) + df = df.reindex(index=indices) + if datafile is not None: + df.to_csv(os.path.join(config.IMDB_DATA_CSV, datafile), + index=False) + return df + +``` + +我们可以将所有三个数据源结合起来,得到 100,000 个文档的列表,如下所示: + +```py +corpus = unsupervised['review'].tolist() + train_df['review'].tolist() + + test_df['review'].tolist() +``` + +我们可以对该语料进行预处理,并将每个文档转换为单词标记序列。 为此,我们使用`nltk`。 然后,我们可以开始进行如下训练。 我们使用了大量的迭代,因此需要 6-8 个小时的时间来训练 CPU: + +```py +# tokenize sentences in corpus +wpt = nltk.WordPunctTokenizer() +tokenized_corpus = [wpt.tokenize(document.lower()) for document in corpus] + +w2v_model = word2vec.Word2Vec(tokenized_corpus, size=50, + window=10, min_count=5, + sample=1e-3, iter=1000) +``` + +现在让我们看看该模型学到了什么。 让我们从这个语料库中选择一些见解的词。 可以在以下位置找到电影评论中通常使用的大量意见词: [http://member.tokoha-u.ac.jp/~dixonfdm/Writing%20Topics%20htm/Movie%20Review%20Folder/movie_descrip_vocab。 htm](http://member.tokoha-u.ac.jp/~dixonfdm/Writing%20Topics%20htm/Movie%20Review%20Folder/movie_descrip_vocab.htm) 。 我们首先将找到与这些给定单词具有相似嵌入的前五个单词。 以下是此代码: + +```py +similar_words = {search_term: [item[0] for item in w2v_model.wv.most_similar([search_term], topn=5)] + for search_term in ['good','superior','violent', + 'romantic','nasty','unfortunate', + 'predictable', 'hilarious', + 'fascinating', 'boring','confused', + 'sensitive', + 'imaginative','senseless', + 'bland','disappointing']} + +pd.DataFrame(similar_words).transpose() +``` + +前面的代码的输出如下: + +![](img/ac5407ea-187e-44b0-9709-449e7be7bdcc.png) + +我们可以看到,学习到的嵌入表示具有相似嵌入向量的相似上下文中使用的单词。 这些词不必一直都是同义词,它们也可以相反。 但是,它们在类似的上下文中使用。 + +# GloVe 模型 + +GloVe 模型代表全局向量,它是一种无监督的学习模型,可用于获取类似于 Word2Vec 的密集词向量。 但是,该技术不同,并且对聚合的全局单词-单词共现矩阵执行训练,从而为我们提供了具有有意义子结构的向量空间。 该方法发表在 Pennington 及其合作者的论文 *GloVe:用于词表示的全局向量*( [https://www.aclweb.org/anthology/D14-1162](https://www.aclweb.org/anthology/D14-1162) )。 我们已经讨论了基于计数的矩阵分解方法,例如**潜在语义分析**( **LSA** )和预测方法,例如 Word2vec。 本文声称,目前这两个家庭都遭受重大弊端。 像 LSA 之类的方法可以有效地利用统计信息,但是它们在词类比任务上的表现相对较差-我们是如何找到语义相似的词的。 像 skip-gram 这样的方法在类比任务上可能会做得更好,但它们在全局级别上却很少利用语料库的统计信息。 + +GloVe 模型的基本方法是首先创建一个庞大的单词-上下文共现矩阵,该矩阵由(单词,上下文)对组成,这样该矩阵中的每个元素都代表一个单词在上下文中出现的频率(可以是一个单词 单词顺序)。 这个词-语境矩阵 *WC* 与在各种任务的文本分析中普遍使用的术语-文档矩阵非常相似。 矩阵分解用于将矩阵 *WC* 表示为两个矩阵的乘积。 **字特征**( **WF** )矩阵和**特征上下文**( **FC** )矩阵。 *WC = WF x FC* 。 用一些随机权重初始化 *WF* 和 *FC* ,然后将它们相乘得到 *WC'*(近似于 *WC* )并进行测量 与 *WC* 有多近。 我们使用**随机梯度下降**( **SGD** )进行多次操作,以最大程度地减少误差。 最后, *WF* 矩阵为我们提供了每个单词的单词嵌入,其中 *F* 可以预设为特定数量的维。 要记住的非常重要的一点是,Word2vec 和 GloVe 模型在工作方式上非常相似。 他们两个的目的都是建立一个向量空间,每个词的位置根据其上下文和语义而受到其相邻词的影响。 Word2vec 从单词共现对的本地单个示例开始,而 GloVe 从整个语料库中所有单词的全局汇总共现统计开始。 + +在以下各节中,我们将同时使用 Word2vec 和 GloVe 来解决各种分类问题。 我们已经开发了一些实用程序代码,可从文件读取和加载 GloVe 和 Word2vec 向量,并返回嵌入矩阵。 预期的文件格式是标准 GloVe 文件格式。 以下是几个单词的五维嵌入格式示例:单词后跟矢量,所有空格分开: + +* 甩动 7.068106 -5.410074 1.430083 -4.482612 -1.079401 +* 心-1.584336 4.421625 -12.552878 4.940779 -5.281123 +* 侧面 0.461367 4.773087 -0.176744 8.251079 -11.168787 +* 恐怖 7.324110 -9.026680 -0.616853 -4.993752 -4.057131 + +以下是读取 GloVe 向量的主要功能,给定一个词汇表作为 Python 字典,字典键作为词汇表中的单词。 仅需要为我们的训练词汇中出现的单词加载所需的嵌入。 同样,用所有嵌入的均值矢量和一些白噪声来初始化 GloVe 嵌入中不存在的词汇。 `0`和`1`行专用于空格和**语音外**( **OOV** )字。 这些单词不在词汇表中,而是在语料库中,例如非常少见的单词或一些过滤掉的杂音。 空间的嵌入是零向量。 OOV 的嵌入是所有其余嵌入的均值向量: + +```py +def _init_embedding_matrix(self, word_index_dict, + oov_words_file='OOV-Words.txt'): + # reserve 0, 1 index for empty and OOV + self.embedding_matrix = np.zeros((len(word_index_dict)+2 , + self.EMBEDDING_DIM)) + not_found_words=0 + missing_word_index = [] + + with open(oov_words_file, 'w') as f: + for word, i in word_index_dict.items(): + embedding_vector = self.embeddings_index.get(word) + if embedding_vector is not None: + # words not found in embedding index will be all- + zeros. + self.embedding_matrix[i] = embedding_vector + else: + not_found_words+=1 + f.write(word + ','+str(i)+'\n') + missing_word_index.append(i) + + #oov by average vector: + self.embedding_matrix[1] = np.mean(self.embedding_matrix, + axis=0) +``` + +```py + for indx in missing_word_index: + self.embedding_matrix[indx] = + np.random.rand(self.EMBEDDING_DIM)+ + self.embedding_matrix[1] + print("words not found in embeddings: + {}".format(not_found_words)) + +``` + +另一个实用程序功能是`update_embeddings`。 这是转学的必要条件。 我们可能希望将一个模型学习的嵌入更新为另一模型学习的嵌入: + +```py +def update_embeddings(self, word_index_dict, other_embedding, other_word_index): + num_updated = 0 + for word, i in other_word_index.items(): + if word_index_dict.get(word) is not None: + embedding_vector = other_embedding[i] + this_vocab_word_indx = word_index_dict.get(word) + self.embedding_matrix[this_vocab_word_indx] = + embedding_vector + num_updated+=1 + + print('{} words are updated out of {}'.format(num_updated, + len(word_index_dict))) +``` + +# CNN 文件模型 + +先前我们看到了词嵌入如何能够捕获它们表示的概念之间的许多语义关系。 现在,我们将介绍一个 ConvNet 文档模型,该模型可构建文档的分层分布式表示形式。 这发表在 Misha Denil 等人的论文 [https://arxiv.org/pdf/1406.3830.pdf](https://arxiv.org/pdf/1406.3830.pdf) 中。 该模型分为两个级别,一个句子级别和一个文档级别,这两个级别都使用 ConvNets 实现。 在句子级别,使用 ConvNet 将每个句子中单词的嵌入转换为整个句子的嵌入。 在文档级别,另一个 ConvNet 用于将句子嵌入转换为文档嵌入。 + +在任何 ConvNet 体系结构中,卷积层之后都是子采样/池化层。 在这里,我们使用 k-max 池。 k-max 合并操作与正常 max 合并略有不同,后者从神经元的滑动窗口获取最大值。 在 k-max 合并操作中,最大的 *k* 神经元取自下一层中的所有神经元。 例如,对[3,1,5,2]应用 2-max 合并将产生[3,5]。 在这里,内核大小为 3 且步幅为 1 的常规最大池将得到相同的结果。 让我们来考虑另一种情况。 如果我们对[1、2、3、4、5]应用最大池,则将得到[3、5],但是 2-max 池将给出[4、5]。 K-max 池可以应用于可变大小的输入,并且我们仍然可以获得相同数量的输出单位。 + +下图描述了**卷积神经网络**( **CNN** )架构。 我们已针对各种用例对该结构进行了一些微调,将在此处进行讨论: + +![](img/91149c74-17ef-432b-8b3a-d7449fc50523.png) + +此网络的输入层未在此处显示。 输入层按顺序是文档中的句子序列,其中每个句子由单词索引序列表示。 以下代码段描述了在给定训练语料库的情况下如何定义单词索引。 索引 0 和 1 保留用于空字和 OOV 字。 首先,将语料库中的文档标记为单词。 非英语单词被过滤掉。 同样,计算整个语料库中每个单词的频率。 对于大型语料库,我们可以从词汇表中过滤掉不常用的词。 然后,为词汇表中的每个单词分配一个整数索引: + +```py +from nltk.tokenize import sent_tokenize, wordpunct_tokenize +import re + +corpus = ['The cat sat on the mat . It was a nice mat !', + 'The rat sat on the mat . The mat was damaged found at 2 places.'] + +vocab ={} +word_index = {} +for doc in corpus: + for sentence in sent_tokenize(doc): + tokens = wordpunct_tokenize(sentence) + tokens = [token.lower().strip() for token in tokens] + tokens = [token for token in tokens + if re.match('^[a-z,.;!?]+$',token) is not None ] + for token in tokens: + vocab[token] = vocab.get(token, 0)+1 +# i= 0 for empty, 1 for OOV +i = 2 +for word, count in vocab.items(): + word_index[word] = i + i +=1 +print(word_index.items()) + +#Here is the output: +dict_items([('the', 2), ('cat', 3), ('sat', 4), ('on', 5), ('mat', 6), ('.', 7), ('it', 8), ('was', #9), ('a', 10), ('nice', 11), ('!', 12), ('rat', 13), ('damaged', 14), ('found', 15), ('at', 16), ('places', 17)]) +``` + +现在,可以将语料库转换为单词索引数组。 在语料库中,不同的句子和文档的长度不同。 尽管卷积可以处理任意宽度的输入,但是为了简化实现,我们可以为网络定义一个固定大小的输入。 我们可以将短句子置零,并截断较长的句子以适应固定的句子长度,并在文档级别执行相同的操作。 在下面的代码片段中,我们显示了如何使用`keras.preprocessing`模块对句子和文档进行零填充并准备数据: + +```py +from keras.preprocessing.sequence import pad_sequences + +SENTENCE_LEN = 10; NUM_SENTENCES=3; +for doc in corpus: + doc2wordseq = [] + sent_num =0 + for sentence in sent_tokenize(doc): + words = wordpunct_tokenize(sentence) + words = [token.lower().strip() for token in words] + word_id_seq = [word_index[word] if word_index.get(word) is not + None \ + else 1 for word in words] + padded_word_id_seq = pad_sequences([word_id_seq], + maxlen=SENTENCE_LEN, + padding='post', + truncating='post') + + if sent_num < NUM_SENTENCES: + doc2wordseq = doc2wordseq + list(padded_word_id_seq[0]) + doc2wordseq = pad_sequences([doc2wordseq], + maxlen=SENTENCE_LEN*NUM_SENTENCES, + padding='post', + truncating='post') + print(doc2wordseq) + +# sample output +[ 2 3 4 5 2 6 7 0 0 0 8 9 10 11 6 12 0 0 0 0 0 0 0 0 0 0 0 0 0 0] +[ 2 13 4 5 2 6 7 0 0 0 2 6 9 14 15 16 1 17 7 0 0 0 0 0 0 0 0 0 0 0] +``` + +因此,您可以看到每个文档输入都是一个尺寸为 *doc_length =* `SENTENCE_LEN` * `NUM_SENTENCES`的一维张量。 这些张量通过网络的第一层,即嵌入层,以将单词索引转换为密集的单词表示形式,然后得到形状为 *doc_length×embedding_dimension* 的二维张量。 所有先前的预处理代码都捆绑在`Preprocess`类中,并具有`fit`和`transform`方法,例如`scikit`模块。 `fit`方法将训练语料库作为输入,构建词汇表,并为词汇表中的每个单词分配单词索引。 然后,可以使用`transform`方法将测试或保留集转换为填充的单词索引序列,如先前所示。 `transform`方法将使用由`fit`计算的单词索引。 + +可以使用 GloVe 或 Word2vec 初始化嵌入矩阵。 在这里,我们使用了 50 维 GloVe 嵌入来初始化嵌入矩阵。 在 GloVe 和 OOV 单词中找不到的单词初始化如下: + +* OOV 单词-训练数据词汇(索引 1)中排除的单词由所有 GloVe 向量的均值初始化 +* 在 GloVe 中找不到的单词由所有 Glove 向量和相同维数的随机向量的均值初始化 + +以下代码段在之前讨论的 GloVe `class`的`_init_embedding_matrix`方法中具有相同的功能: + +```py +#oov by average vector: +self.embedding_matrix[1] = np.mean(self.embedding_matrix, axis=0) +for indx in missing_word_index: + self.embedding_matrix[indx] = np.random.rand(self.EMBEDDING_DIM)+ + self.embedding_matrix[1] +``` + +初始化嵌入矩阵之后,我们现在可以构建第一层,即嵌入层,如下所示: + +```py +from keras.layers import Embedding +embedding_layer = Embedding(vocab_size, + embedding_dim, + weights=[embedding_weights], + input_length=max_seq_length, + trainable=True, + name='embedding') +``` + +接下来,我们必须构建单词卷积层。 我们希望在所有句子上应用相同的一维卷积滤波器,也就是说,在所有句子之间共享相同的卷积滤波器权重。 首先,我们使用`Lambda`层将输入分成句子。 然后,如果我们使用 *C* 卷积滤波器,则每个句子的二维张量形状( *SENTENCE_LEN×EMBEDDING _DIM* )将转换为(([ *SENTENCE_LEN-filter + 1)×C)*张量。 以下代码执行相同的操作: + +```py +#Let's take sentence_len=30, embedding_dim=50, num_sentences = 10 +#following convolution filters to be used for all sentences. +word_conv_model = Conv1D(filters= 6, + kernel_size= 5, + padding="valid", + activation="relu", + trainable = True, + name = "word_conv", + strides=1) + +for sent in range(num_sentences): + ##get one sentence from the input document + sentence = Lambda(lambda x : x[:, sent*sentence_len: + (sent+1)*sentence_len, :])(z) + ##sentence shape : (None, 30, 50) + conv = word_conv_model(sentence) + ## convolution shape : (None, 26, 6) + +``` + +k-max 合并层在`keras`中不可用。 我们可以将 k-max 池实现为自定义层。 要实现自定义层,我们需要实现三种方法: + +* `call(x)`:这是实现层逻辑的地方 +* `compute_output_shape(input_shape)`:如果自定义层修改了其输入的形状 +* `build(input_shape)`:定义图层权重(我们不需要此,因为我们的图层没有权重) + +这是 k-max 合并层的完整代码: + +```py +import tensorflow as tf +from keras.layers import Layer, InputSpec + +class KMaxPooling(Layer): + def __init__(self, k=1, **kwargs): + super().__init__(**kwargs) + self.input_spec = InputSpec(ndim=3) + self.k = k + + def compute_output_shape(self, input_shape): + return (input_shape[0], (input_shape[2] * self.k)) + + def call(self, inputs): + + # swap last two dimensions since top_k will be + # applied along the last dimension + shifted_input = tf.transpose(inputs, [0, 2, 1]) + + # extract top_k, returns two tensors [values, indices] + top_k = tf.nn.top_k(shifted_input, k=self.k, sorted=True, + name=None)[0] + + # return flattened output + return top_k + +``` + +将前面的 k-max 池化层应用于单词卷积,我们得到句子嵌入层: + +```py +for sent in range(num_sentences): + ##get one sentence from the input document + sentence = Lambda(lambda x : x[:,sent*sentence_len: + (sent+1)*sentence_len, :])(z) + ##sentence shape : (None, 30, 50) + conv = word_conv_model(sentence) + ## convolution shape : (None, 26, 6) + conv = KMaxPooling(k=3)(conv) + #transpose pooled values per sentence + conv = Reshape([word_filters*sent_k_maxpool,1])(conv) + ## shape post k-max pooling and reshape (None, 18=6*3, 1) +``` + +因此,我们将形状为 30×50 的每个句子转换为 18×1,然后将这些张量连接起来以获得句子嵌入。 我们使用 Keras 中的`Concatenate`层来实现相同的功能: + +```py +z = Concatenate()(conv_blocks) if len(conv_blocks) > 1 else conv_blocks[0] +z = Permute([2,1], name='sentence_embeddings')(z) +## output shape of sentence embedding is : (None, 10, 18) +``` + +如前所述,对前一句子嵌入应用一维卷积,然后进行 k-max 合并,以获得*文档*嵌入。 这样就完成了文本的文档模型。 根据手头的学习任务,可以定义下一层。 对于分类任务,可以将文档嵌入连接到密集层,然后连接到具有 *K* 单位的最终 softmax 层,以解决 k 类分类问题。 在最后一层之前,我们可以有多个致密层。 以下代码段实现了相同的功能: + +```py +sent_conv = Conv1D(filters=16, + kernel_size=3, + padding="valid", + activation="relu", + trainable = True, + name = 'sentence_conv', + strides=1)(z) + +z = KMaxPooling(k=5)(sent_conv) +z = Flatten(name='document_embedding')(z) + +for i in range(num_hidden_layers): + layer_name = 'hidden_{}'.format(i) + z = Dense(hidden_dims, activation=hidden_activation, + name=layer_name)(z) + +model_output = Dense(K, activation='sigmoid',name='final')(z) +``` + +整个代码包含在`cnn_document_model`模块中。 + +# 建立评论情绪分类器 + +现在,通过训练前面的 CNN 文档模型来构建情感分类器。 我们将使用来自 [https://www.kaggle.com/bittlingmayer/amazonreviews](https://www.kaggle.com/bittlingmayer/amazonreviews) 的“亚马逊情感分析评论”数据集来训练该模型。 该数据集由数百万个 Amazon 客户评论(输入文本)和星级(输出标签)组成。 数据格式如下:标签,后跟空格,审阅标题,后跟`:`和空格,位于审阅文本之前。 该数据集比流行的 IMDB 电影评论数据集大得多。 此外,此数据集包含各种产品和电影的相当多的评论集: + +```py +__label__ : + +Example: +__label__2 Good Movie: Awesome.... simply awesome. I couldn't put this down and laughed, smiled, and even got tears! A brand new favorite author. +``` + +在此,`__label__1`对应于 1 星和 2 星评论,`__label__2`对应于 4 星和 5 星评论。 但是,此数据集中未包含三星级评论,即具有中性情绪的评论。 在此数据集中,我们总共有 360 万个培训示例和 40 万个测试示例。 我们将从训练示例中随机抽取一个大小为 200,000 的样本,以便我们可以猜测一个很好的超参数来进行训练: + +```py +train_df = Loader.load_amazon_reviews('train') +print(train_df.shape) + +test_df = Loader.load_amazon_reviews('test') +print(test_df.shape) + +dataset = train_df.sample(n=200000, random_state=42) +dataset.sentiment.value_counts() +``` + +接下来,我们使用`Preprocess`类将语料库转换为填充的单词索引序列,如下所示: + +```py +preprocessor = Preprocess() +corpus_to_seq = preprocessor.fit(corpus=corpus) + +holdout_corpus = test_df['review'].values +holdout_target = test_df['sentiment'].values +holdout_corpus_to_seq = preprocessor.transform(holdout_corpus) +``` + +让我们使用`GloVe`类用 GloVe 初始化嵌入,并构建文档模型。 我们还需要定义文档模型参数,例如卷积过滤器的数量,激活函数,隐藏单元等。 为了避免网络的过度拟合,我们可以在输入层,卷积层甚至最终层或密集层之间插入丢失层。 同样,正如我们在密集层所观察到的,放置高斯噪声层可作为更好的正则化器。 可以使用以下定义的所有这些参数初始化`DocumentModel`类。 为了对模型参数进行良好的初始化,我们从少量的时期和少量的采样训练示例开始。 最初,我们开始使用六个词卷积过滤器(如针对 IMDB 数据的论文所述),然后发现该模型不适合-训练的精度未超过 80%,然后我们继续缓慢地增加词过滤器的数量 。 同样,我们发现了大量的句子卷积过滤器。 我们尝试了卷积层的 ReLU 和 tanh 激活。 如论文 [https://arxiv.org/pdf/1406.3830.pdf](https://arxiv.org/pdf/1406.3830.pdf) 所述,他们将 tanh 激活用于其模型: + +```py +glove=GloVe(50) +initial_embeddings = glove.get_embedding(preprocessor.word_index) + +amazon_review_model = + DocumentModel(vocab_size=preprocessor.get_vocab_size(), + word_index = preprocessor.word_index, + num_sentences = Preprocess.NUM_SENTENCES, + embedding_weights = initial_embeddings, + conv_activation = 'tanh', + hidden_dims=64, + input_dropout=0.40, + hidden_gaussian_noise_sd=0.5) +``` + +以下是此模型的参数的完整列表,我们已将其用于训练了 360 万个完整的训练示例: + +```py +{ + "embedding_dim":50, + "train_embedding":true, + "sentence_len":30, + "num_sentences":10, + "word_kernel_size":5, + "word_filters":30, + "sent_kernel_size":5, + "sent_filters":16, + "sent_k_maxpool":3, + "input_dropout":0.4, + "doc_k_maxpool":4, + "sent_dropout":0, + "hidden_dims":64, + "conv_activation":"relu", + "hidden_activation":"relu", + "hidden_dropout":0, + "num_hidden_layers":1, + "hidden_gaussian_noise_sd":0.5, + "final_layer_kernel_regularizer":0.0, + "learn_word_conv":true, + "learn_sent_conv":true + } +``` + +最后,在开始全面培训之前,我们需要确定一个好的批次大小。 对于大批量(如 256),训练非常慢,因此我们使用了`64`的批量。 我们使用`rmsprop`优化器来训练我们的模型,并从`keras`使用的默认学习率开始。 以下是训练参数的完整列表,它们存储在`TrainingParameters`类中: + +```py + {"seed":55, + "batch_size":64, + "num_epochs":35, + "validation_split":0.05, + "optimizer":"rmsprop", + "learning_rate":0.001} +``` + +以下是开始训练的代码: + +```py +train_params = TrainingParameters('model_with_tanh_activation') + +amazon_review_model.get_classification_model().compile( + loss="binary_crossentropy", + optimizer= + train_params.optimizer, + metrics=["accuracy"]) +checkpointer = ModelCheckpoint(filepath=train_params.model_file_path, + verbose=1, + save_best_only=True, + save_weights_only=True) + +x_train = np.array(corpus_to_seq) +y_train = np.array(target) + +x_test = np.array(holdout_corpus_to_seq) +y_test = np.array(holdout_target) + +amazon_review_model.get_classification_model().fit(x_train, y_train, + batch_size=train_params.batch_size, + epochs=train_params.num_epochs, + verbose=2, + validation_split=train_params.validation_split, + callbacks=[checkpointer]) +``` + +我们已经在 CPU 上训练了该模型,下面是五个时期后的结果。 对于 190k 样本,只有一个时期非常慢,大约需要 10 分钟才能运行。 但是,您可以在下面看到,在五个时期之后的训练和验证准确性达到 **92%**,这是相当不错的: + +```py +Train on 190000 samples, validate on 10000 samples + Epoch 1/35 + - 577s - loss: 0.3891 - acc: 0.8171 - val_loss: 0.2533 - val_acc: 0.8369 + Epoch 2/35 + - 614s - loss: 0.2618 - acc: 0.8928 - val_loss: 0.2198 - val_acc: 0.9137 + Epoch 3/35 + - 581s - loss: 0.2332 - acc: 0.9067 - val_loss: 0.2105 - val_acc: 0.9191 + Epoch 4/35 + - 640s - loss: 0.2197 - acc: 0.9128 - val_loss: 0.1998 - val_acc: 0.9206 + Epoch 5/35 + ... + ... +``` + +我们对 40 万条评论进行了评估,对模型进行了评估,结果**的准确度也达到 92%**。 这清楚地表明该模型非常适合此审阅数据,并且随着数据的增加,还有更多的改进空间。 到目前为止,在整个培训过程中,转移学习的主要用途是用于初始化单词嵌入的 GloVe 嵌入向量。 在这里,由于我们拥有大量数据,因此我们可以从头开始学习权重。 但是,让我们看看在整个训练过程中,哪些词嵌入更新最多。 + +# 嵌入变化最大的是什么? + +我们可以采用初始 GloVe 嵌入和最终学习的嵌入,并通过对每个单词的差异进行归一化来比较它们。 然后,我们可以对标准值进行排序,以查看哪些词变化最大。 这是执行此操作的代码: + +```py +learned_embeddings = amazon_review_model.get_classification_model() + .get_layer('embedding').get_weights()[0] + +embd_change = {} +for word, i in preprocessor.word_index.items(): + embd_change[word] = np.linalg.norm(initial_embeddings[i]- + learned_embeddings[i]) +embd_change = sorted(embd_change.items(), key=lambda x: x[1], + reverse=True) +embd_change[0:20] +``` + +您可以检查是否最新的嵌入是针对意见词的。 + +# 转移学习–应用到 IMDB 数据集 + +我们应该使用转移学习的一种情况是,手头任务的标签数据少得多,而相似但不同的领域的培训数据很多。 IMDB 数据集( [http://ai.stanford.edu/~amaas/data/sentiment/](http://ai.stanford.edu/~amaas/data/sentiment/) )是二进制情​​感分类数据集。 它拥有 25,000 条用于培训的电影评论和 25,000 条用于测试的电影评论。 关于此数据集,有很多已发表的论文,并且可能通过 Le 和 Mikolov 的段落向量( [https://arxiv.org/pdf/1405.4053.pdf](https://arxiv.org/pdf/1405.4053.pdf) )在此数据集上获得最佳结果 来自 Google。 他们在此数据集上实现了 **92.58%**的准确性。 SVM 达到了 89%。 这个数据集的大小不错,我们可以从头开始训练 CNN 模型。 这为我们提供了与 SVM 相当的结果。 下一节将对此进行讨论。 + +现在,让我们尝试使用少量的 IMDB 数据样本(例如 **5%**)构建模型。 在许多实际情况下,我们面临训练数据不足的问题。 我们无法使用此小型数据集训练 CNN。 因此,我们将使用转移学习为该数据集构建模型。 + +我们首先按照与其他数据集相同的步骤预处理和准备数据: + +```py +train_df = Loader.load_imdb_data(directory = 'train') +train_df = train_df.sample(frac=0.05, random_state = train_params.seed) +#take only 5% +print(train_df.shape) + +test_df = Loader.load_imdb_data(directory = 'test') +print(test_df.shape) + +corpus = train_df['review'].tolist() +target = train_df['sentiment'].tolist() +corpus, target = remove_empty_docs(corpus, target) +print(len(corpus)) + +preprocessor = Preprocess(corpus=corpus) +corpus_to_seq = preprocessor.fit() + +test_corpus = test_df['review'].tolist() +test_target = test_df['sentiment'].tolist() +test_corpus, test_target = remove_empty_docs(test_corpus, test_target) +print(len(test_corpus)) + +test_corpus_to_seq = preprocessor.transform(test_corpus) + +x_train = np.array(corpus_to_seq) +x_test = np.array(test_corpus_to_seq) + +y_train = np.array(target) +y_test = np.array(test_target) + +print(x_train.shape, y_train.shape) + +glove=GloVe(50) +initial_embeddings = glove.get_embedding(preprocessor.word_index) + +#IMDB MODEL +``` + +现在,让我们先加载训练后的模型。 我们有两种加载方法:模型的超参数和`DocumentModel`类中学习的模型权重: + +```py +def load_model(file_name): + with open(file_name, "r", encoding= "utf-8") as hp_file: + model_params = json.load(hp_file) + doc_model = DocumentModel( **model_params) + print(model_params) + return doc_model + +def load_model_weights(self, model_weights_filename): + self._model.load_weights(model_weights_filename, by_name=True) +``` + +然后,我们使用前述方法加载预训练的模型,然后按如下方法将学习到的权重转移到新模型中。 预训练模型的嵌入矩阵比语料库更大,单词更多。 因此,我们不能直接使用预训练模型中的嵌入矩阵。 我们将使用`GloVe`类中的`update_embedding`方法,使用经过训练的模型中的嵌入来更新 IMDB 模型的 GloVe 初始化的嵌入: + +```py +amazon_review_model = DocumentModel.load_model("model_file.json") +amazon_review_model.load_model_weights("model_weights.hdf5") +learned_embeddings = amazon_review_model.get_classification_model()\ + .get_layer('embedding').get_weights()[0] + +#update the GloVe embeddings. +glove.update_embeddings(preprocessor.word_index, + np.array(learned_embeddings), + amazon_review_model.word_index) +``` + +现在,我们都准备建立迁移学习模型。 让我们首先构建 IMDB 模型,然后从其他预训练模型初始化权重。 我们不会使用少量数据来训练该网络的较低层。 因此,我们将为其设置`trainable=False`。 我们将仅训练具有较大辍学率的最后一层: + +```py +initial_embeddings = glove.get_embedding(preprocessor.word_index)#get + updated embeddings + +imdb_model = DocumentModel(vocab_size=preprocessor.get_vocab_size(), + word_index = preprocessor.word_index, + num_sentences=Preprocess.NUM_SENTENCES, + embedding_weights=initial_embeddings, + conv_activation = 'tanh', + train_embedding = False, + learn_word_conv = False, + learn_sent_conv = False, + hidden_dims=64, + input_dropout=0.0, + hidden_layer_kernel_regularizer=0.001, + final_layer_kernel_regularizer=0.01) + +#transfer word & sentence conv filters +for l_name in ['word_conv','sentence_conv','hidden_0', 'final']: + imdb_model.get_classification_model()\ + .get_layer(l_name).set_weights(weights=amazon_review_model + .get_classification_model() + .get_layer(l_name).get_weights()) +``` + +在经过几个时期的训练之后,仅对隐藏层和最终的 S 型层进行了微调,我们在 25k 测试集上获得了 **86%**测试精度。 如果我们尝试在这个小的数据集上训练 SVM 模型并预测整个 25k 测试集,则只能获得 82%的准确性。 因此,即使我们的数据较少,转移学习显然也有助于建立更好的模型。 + +# 使用 Word2vec 嵌入对完整 IMDB 数据集进行培训 + +现在,让我们尝试通过转移学习到的 Word2vec 嵌入,在完整的 IMDB 数据集上训练文档 CNN 模型。 + +请注意,我们没有使用从 Amazon Review 模型中学到的权重。 我们将从头开始训练模型。 实际上,这就是本文所做的。 + +此代码与前面的 IMDB 培训代码非常相似。 您只需要从 Amazon 模型中排除重量加载部分。 该代码位于存储库中名为`imdb_model.py`的模块中。 另外,这是模型参数: + +```py +{ + "embedding_dim":50, + "train_embedding":true, + "embedding_regularizer_l2":0.0, + "sentence_len":30, + "num_sentences":20, + "word_kernel_size":5, + "word_filters":30, + "sent_kernel_size":5, + "sent_filters":16, + "sent_k_maxpool":3, + "input_dropout":0.4, + "doc_k_maxpool":5, + "sent_dropout":0.2, + "hidden_dims":64, + "conv_activation":"relu", + "hidden_activation":"relu", + "hidden_dropout":0, + "num_hidden_layers":1, + "hidden_gaussian_noise_sd":0.3, + "final_layer_kernel_regularizer":0.04, + "hidden_layer_kernel_regularizer":0.0, + "learn_word_conv":true, + "learn_sent_conv":true, + "num_units_final_layer":1 + } +``` + +训练时,我们使用了另一种技巧来避免过度拟合。 我们在前 10 个时间段后冻结嵌入层(即`train_embedding=False`),仅训练其余层。 经过 50 个纪元后,我们在 IMDB 数据集上实现了 **89%**的准确度,这是本文提出的结果。 我们观察到,如果我们在训练之前不初始化嵌入权重,则模型将开始过度拟合,并且无法实现 **80%**以上的准确性验证。 + +# 使用 CNN 模型创建文档摘要 + +评论有很多句子。 这些句子中的一些是中性的,而某些则是多余的,无法确定整个文档的极性。 总结评论或在评论中突出显示用户实际表达意见的句子非常有用。 实际上,它也为我们所做的预测提供了解释,从而使模型可以解释。 + +如本文所述,文本摘要的第一步是通过为每个句子分配重要性分数来为文档创建显着性图。 为了生成给定文档的显着性图,我们可以应用以下技术: + +1. 我们首先通过网络执行前向传递,以生成文档的类别预测。 +2. 然后,我们通过反转网络预测来构造伪标签。 +3. 将伪标签作为真实标签输入到训练损失函数中。 伪标签的这种选择使我们能够造成最大的损失。 反过来,这将使反向传播修改对决定类标签贡献最大的句子嵌入的权重。 因此,在实际上是肯定标签的情况下,如果我们将 0 作为伪标签传递,则强阳性语句嵌入应该会看到最大的变化,即高梯度范数。 +4. 计算损失函数相对于句子嵌入层的导数。 +5. 按梯度范数按降序对句子进行排序,以使最重要的句子排在顶部。 + +让我们在 Keras 中实现它。 我们必须像以前一样进行预处理,并获得`x_train`和`y_train` NumPy 数组。 我们将首先加载训练有素的 IMDB 模型和学习的权重。 然后,我们还需要使用用于从该模型中获取导数和损失函数的优化器来编译模型: + +```py +imdb_model = DocumentModel.load_model(config.MODEL_DIR+ + '/imdb/model_02.json') +imdb_model.load_model_weights(config.MODEL_DIR+ '/imdb/model_02.hdf5') + +model = imdb_model.get_classification_model() +model.compile(loss="binary_crossentropy", optimizer='rmsprop', + metrics=["accuracy"]) +``` + +现在,让我们进行前面提到的*步骤 1* ,即前向传递,然后生成伪标签: + +```py +preds = model.predict(x_train) +#invert predicted label +pseudo_label = np.subtract(1,preds) +``` + +为了计算梯度,我们将使用 Keras 函数`model.optimizer.get_gradients()`: + +```py +#Get the learned sentence embeddings +sentence_ebd = imdb_model.get_sentence_model().predict(x_train) + +input_tensors = [model.inputs[0], # input data +# how much to weight each sample by + model.sample_weights[0], + model.targets[0], # labels + ] +#variable tensor at the sentence embedding layer +weights = imdb_model.get_sentence_model().outputs + +#calculate gradient of the total model loss w.r.t +#the variables at sentence embd layer +gradients = model.optimizer.get_gradients(model.total_loss, weights) +get_gradients = K.function(inputs=input_tensors, outputs=gradients) +``` + +现在,我们可以计算出一个文档(例如,文档编号`10`)的梯度,如下所示: + +```py +document_number = 10 +K.set_learning_phase(0) +inputs = [[x_train[document_number]], # X + [1], # sample weights + [[pseudo_label[document_number][0]]], # y +] +grad = get_gradients(inputs) +``` + +现在,我们可以按梯度范数对句子进行排序。 我们将使用与预处理中使用的相同的`nltk sent_tokenize`函数来获取文本句子: + +```py +sent_score = [] +for i in range(Preprocess.NUM_SENTENCES): + sent_score.append((i, -np.linalg.norm(grad[0][0][i]))) + +sent_score.sort(key=lambda tup: tup[1]) +summary_sentences = [ i for i, s in sent_score[:4]] + +doc = corpus[document_number] +label = y_train[document_number] +prediction = preds[document_number] +print(doc, label , prediction) + +sentences = sent_tokenize(doc) +for i in summary_sentences: + print(i, sentences[i]) +``` + +以下是**否定的**评论: + +```py +Wow, what a great cast! Julia Roberts, John Cusack, Christopher Walken, Catherine Zeta-Jones, Hank Azaria...what's that? A script, you say? Now you're just being greedy! Surely such a charismatic bunch of thespians will weave such fetching tapestries of cinematic wonder that a script will be unnecessary? You'd think so, but no. America's Sweethearts is one missed opportunity after another. It's like everyone involved woke up before each day's writing/shooting/editing and though "You know what? I've been working pretty hard lately, and this is guaranteed to be a hit with all these big names, right? I'm just gonna cruise along and let somebody else carry the can." So much potential, yet so painful to sit through. There isn't a single aspect of this thing that doesn't suck. Even Julia's fat suit is lame. +``` + +从前两个句子看来,这是非常积极的。 我们对该文件的预测得分是 0.15,这是正确的。 让我们看看我们得到了什么总结: + +```py + 4 Surely such a charismatic bunch of thespians will weave such + fetching tapestries of cinematic wonder that a script will be + unnecessary? + 2 A script, you say? + 6 America's Sweethearts is one missed opportunity after another. +``` + +让我们再举一个积极的例子,这里我们的模型预测为 0.98: + +```py +This is what I was expecting when star trek DS9 premiered. Not to slight DS9\. That was a wonderful show in it's own right, however it never really gave the fans more of what they wanted. Enterprise is that show. While having a similarity to the original trek it differs enough to be original in it's own ways. It makes the ideas of exploration exciting to us again. And that was one of the primary ingredients that made the original so loved. Another ingredient to success was the relationships that evolved between the crew members. Viewers really cared deeply for the crew. Enterprise has much promise in this area as well. The chemistry between Bakula and Blalock seems very promising. While sexual tension in a show can often become a crutch, I feel the tensions on enterprise can lead to much more and say alot more than is typical. I think when we deal with such grand scale characters of different races or species even, we get some very interesting ideas and television. Also, we should note the performances, Blalock is very convincing as Vulcan T'pol and Bacula really has a whimsy and strength of character that delivers a great performance. The rest of the cast delivered good performances also. My only gripes are as follows. The theme. It's good it's different, but a little to light hearted for my liking. We need something a little more grand. Doesn't have to be orchestral. Maybe something with a little more electronic sound would suffice. And my one other complaint. They sell too many adds. They could fix this by selling less ads, or making all shows two parters. Otherwise we'll end up seeing the shows final act getting wrapped up way too quickly as was one of my complaints of Voyager. +``` + +这是摘要: + +```py + 2 That was a wonderful show in it's own right, however it never really + gave the fans more of what they wanted. + 5 It makes the ideas of exploration exciting to us again. + 6 And that was one of the primary ingredients that made the original + so loved. + 8 Viewers really cared deeply for the crew. +``` + +您会看到它很好地掌握了摘要句子。 您真的不需要通过整个审查来理解它。 因此,此文本 CNN 模型可与 IMDB 数据集的最新模型相媲美,而且一旦学习,它就可以执行其他高级文本分析任务,例如文本摘要。 + +# 使用 CNN 模型进行多类分类 + +现在,我们将相同的模型应用于多类分类。 我们将为此使用 20 个新闻组数据集。 为了训练 CNN 模型,此数据集很小。 我们仍然会尝试解决一个更简单的问题。 如前所述,该数据集中的 20 个类有很多混合,使用 SVM,我们可以获得最高 70%的准确性。 在这里,我们将采用该数据集的六大类,并尝试构建 CNN 分类器。 因此,首先我们将 20 个类别映射到 6 个大类别。 以下是首先从 scikit Learn 加载数据集的代码: + +```py +def load_20newsgroup_data(categories = None, subset='all'): + data = fetch_20newsgroups(subset=subset, + shuffle=True, + remove=('headers', 'footers', 'quotes'), + categories = categories) + return data + +dataset = Loader.load_20newsgroup_data(subset='train') +corpus, labels = dataset.data, dataset.target + +test_dataset = Loader.load_20newsgroup_data(subset='test') +test_corpus, test_labels = test_dataset.data, test_dataset.target +``` + +接下来,我们将 20 个类映射到六个类别,如下所示: + +```py +six_groups = { + 'comp.graphics':0,'comp.os.ms- + windows.misc':0,'comp.sys.ibm.pc.hardware':0, + 'comp.sys.mac.hardware':0, 'comp.windows.x':0, + + 'rec.autos':1, 'rec.motorcycles':1, 'rec.sport.baseball':1, + 'rec.sport.hockey':1, + 'sci.crypt':2, 'sci.electronics':2,'sci.med':2, 'sci.space':2, + 'misc.forsale':3, + 'talk.politics.misc':4, 'talk.politics.guns':4, + 'talk.politics.mideast':4, + 'talk.religion.misc':5, 'alt.atheism':5, 'soc.religion.christian':5 + } + +map_20_2_6 = [six_groups[dataset.target_names[i]] for i in range(20)] +labels = [six_groups[dataset.target_names[i]] for i in labels] +test_labels = [six_groups[dataset.target_names[i]] for i in + test_labels] +``` + +我们将执行相同的预处理步骤,然后进行模型初始化。 同样,在这里,我们使用了 GloVe 嵌入来初始化单词嵌入向量。 详细代码在`20newsgrp_model`模块的存储库中。 这是模型的超参数: + +```py +{ + "embedding_dim":50, + "train_embedding":false, + "embedding_regularizer_l2":0.0, + "sentence_len":30, + "num_sentences":10, + "word_kernel_size":5, + "word_filters":30, + "sent_kernel_size":5, + "sent_filters":20, + "sent_k_maxpool":3, + "input_dropout":0.2, + "doc_k_maxpool":4, + "sent_dropout":0.3, + "hidden_dims":64, + "conv_activation":"relu", + "hidden_activation":"relu", + "hidden_dropout":0, + "num_hidden_layers":2, + "hidden_gaussian_noise_sd":0.3, + "final_layer_kernel_regularizer":0.01, + "hidden_layer_kernel_regularizer":0.0, + "learn_word_conv":true, + "learn_sent_conv":true, + "num_units_final_layer":6 + } +``` + +这是测试集上模型的详细结果: + +```py + precision recall f1-score support + 0 0.80 0.91 0.85 1912 + 1 0.86 0.85 0.86 1534 + 2 0.75 0.79 0.77 1523 + 3 0.88 0.34 0.49 382 + 4 0.78 0.76 0.77 1027 + 5 0.84 0.79 0.82 940 + + avg / total 0.81 0.80 0.80 7318 + + [[1733 41 114 1 14 9] + [ 49 1302 110 11 47 15] + [ 159 63 1196 5 75 25] + [ 198 21 23 130 9 1] + [ 10 53 94 0 782 88] + [ 22 30 61 0 81 746]] + 0.8047280677780815 +``` + +让我们在此数据集上尝试 SVM,看看我们获得的最佳精度是多少: + +```py +from sklearn.feature_extraction.text import TfidfVectorizer +from sklearn.svm import SVC +tv = TfidfVectorizer(use_idf=True, min_df=0.00005, max_df=1.0, + ngram_range=(1, 1), stop_words = 'english', + sublinear_tf=True) +tv_train_features = tv.fit_transform(corpus) +tv_test_features = tv.transform(test_corpus) + +clf = SVC(C=1,kernel='linear', random_state=1, gamma=0.01) +svm=clf.fit(tv_train_features, labels) +preds_test = svm.predict(tv_test_features) + +from sklearn.metrics import + classification_report,accuracy_score,confusion_matrix + +print(classification_report(test_labels, preds_test)) +print(confusion_matrix(test_labels, preds_test)) +print(accuracy_score(test_labels, preds_test)) +``` + +以下是 SVM 模型的结果。 我们已经对参数 *C* 进行了调整,以便获得最佳的交叉验证精度: + +```py + precision recall f1-score support + + 0 0.86 0.89 0.87 1912 + 1 0.83 0.89 0.86 1534 + 2 0.75 0.78 0.76 1523 + 3 0.87 0.73 0.80 382 + 4 0.82 0.75 0.79 1027 + 5 0.85 0.76 0.80 940 + + avg / total 0.82 0.82 0.82 7318 + + 0.82344902978956 +``` + +因此,我们看到,在多类分类结果的情况下,本文的 CNN 模型也可以给出可比较的结果。 再次,和以前一样,现在也可以使用经过训练的模型来执行文本摘要。 + +# 可视化文档嵌入 + +在我们的文档 CNN 模型中,我们具有文档嵌入层。 让我们尝试可视化模型在这一层中学到的功能。 我们将首先获取测试集,并按如下方式计算文档嵌入: + +```py +doc_embeddings = newsgrp_model.get_document_model().predict(x_test) +print(doc_embeddings.shape) + +(7318, 80) +``` + +我们为所有测试文档获得了 80 维嵌入向量。 为了可视化这些向量,我们将使用流行的 t-SNE 二维还原技术将向量投影到二维空间中,并绘制散点图,如下所示: + +```py +from utils import scatter_plot + +doc_proj = TSNE(n_components=2, random_state=42, + ).fit_transform(doc_embeddings) +f, ax, sc, txts = scatter_plot(doc_proj, np.array(test_labels)) +``` + +前面代码的输出如下: + +![](img/84f83a45-f09c-4a0e-8da2-e760138193ea.png) + +散点图上的标签(0-5)代表六个类别。 如您所见,该模型学习了不错的嵌入,并且能够在 80 维空间中很好地分离出六个类。 我们可以将这些嵌入用于其他文本分析任务,例如*信息检索*或*文本搜索*。 给定一个查询文档,我们可以计算其密集嵌入,然后将其与整个语料库中的相似嵌入进行比较。 这可以帮助我们提高基于关键字的查询结果并提高检索性能。 + +# 摘要 + +我们已经从自然语言处理,文本分类,文本摘要以及深度学习 CNN 模型在文本域中的应用中学习了一些概念。 我们已经看到,在大多数用例中,尤其是如果我们的训练数据较少时,默认的第一步就是以词嵌入为基础的转移学习。 我们已经看到了如何将转移学习应用于在巨大的 Amazon 产品评论数据集上学习的文本 CNN 模型,以对小型电影评论数据集(相关但不相同的领域)进行预测。 + +此外,我们在这里还学习了如何将学习到的 CNN 模型用于其他文本处理任务,例如将文档汇总和表示为密集矢量,这些信息可以在信息检索系统中使用,以提高检索性能。 \ No newline at end of file diff --git a/docs/handson-tl-py/8.md b/docs/handson-tl-py/8.md new file mode 100644 index 0000000000000000000000000000000000000000..27088e5a53431c91a62846e492f71eb4af556138 --- /dev/null +++ b/docs/handson-tl-py/8.md @@ -0,0 +1,906 @@ +# 音频事件识别与分类 + +在前面的章节中,我们已经研究了一些非常有趣的案例研究,这些案例将转移学习应用于实际问题。 图像和文本数据是我们先前已解决的两种非结构化数据形式。 我们已经展示了各种方法来应用迁移学习来获得更强大和更出色的模型,以及解决诸如缺少训练数据之类的约束。 在本章中,我们将解决识别和分类音频事件的新现实问题。 + +为音频数据创建预训练的深度学习模型是一个巨大的挑战,因为我们没有高效的预训练的视觉模型(例如 VGG 或 Inception(适用于图像数据)或基于词嵌入的模型(如 Word2vec 或 GloVe)的优势) 文字数据)。 然后可能会出现一个问题,那就是我们对音频数据的策略是什么。 我们将在本章中探索一些创新方法,敬请期待! 本章将涵盖以下主要方面: + +* 了解音频事件分类 +* 制定我们的现实问题 +* 探索性音频事件分析 +* 功能工程和音频事件的表示 +* 带有转移学习的音频事件分类 +* 构建深度学习音频事件标识符 + +在本章中,我们将研究识别和分类音频事件的实际案例研究。 诸如音频特征工程,转换学习,深度学习和面向对象编程等概念将用于构建健壮的,自动化的音频事件标识符以进行分类。 您可以在 GitHub 存储库中的 [https://github.com/dipanjanS/hands-on-transfer-learning-with-python](https://github.com/dipanjanS/hands-on-transfer-learning-with-python) 的 GitHub 存储库的`Chapter 8`文件夹中快速阅读本章的代码。 可以根据需要参考本章。 + +# 了解音频事件分类 + +到现在为止,您应该了解分类或分类的基本任务,在这里我们已经有了结构化或非结构化的数据,这些数据通常用特定的组或类别进行标记或注释。 自动分类的主要任务是建立一个模型,以便使用未来的数据点,我们可以根据各种数据属性或特征将每个数据点分类或记录为一种特定的类别。 + +在前面的章节中,我们已经研究了文本和图像的分类。 在本章中,我们将研究对音频事件进行分类。 音频事件基本上是通常由音频信号捕获的事件或活动的发生。 通常,短的音频片段用于表示音频事件,因为即使它们反复出现,声音通常也很相似。 但是,有时,可能会使用更长的音频剪辑来表示更复杂的音频事件。 音频事件的示例可能是儿童在操场上玩耍,警笛警报,狗吠等。 实际上,谷歌已经建立了一个名为 **AudioSet** ( [https://research.google.com/audioset/index.html](https://research.google.com/audioset/index.html) )的海量数据集,它是一个手动的大规模数据集 带注释的音频事件,他们还发表了几篇有关音频事件识别和分类的论文。 我们将使用较小的数据集来解决问题,但有兴趣的读者一定应该查看这个庞大的数据集,其中包含 632 个音频事件类,其中包括从 YouTube 视频中提取的 208420 个人工标记的 10 秒声音剪辑的集合。 + +# 制定我们的现实问题 + +我们这里的实际案例研究的主要目标是音频事件的识别和分类。 这是一个有监督的学习问题,我们将在音频事件数据集上使用属于特定类别(它们是声音的来源)的音频数据样本进行处理。 + +我们将利用迁移学习和深度学习中的概念来构建可靠的分类器,从而在任何给定音频样本属于我们预定类别之一的情况下,我们都应该能够正确预测该声音的来源。 我们将使用的数据集通常被称为 UrbanSound8K 数据集( [https://urbansounddataset.weebly.com/](https://urbansounddataset.weebly.com/) ),并且具有 8,732 个带标签的音频声音文件(其持续时间通常等于或大于 少于 4 秒),其中包含城市常见声音的摘录。 该数据集中的声音的十个类别如下: + +* `air_conditioner` +* `car_horn` +* `children_playing` + +* `dog_bark` +* `drilling` +* `engine_idling` +* `gun_shot` +* `jackhammer` +* `siren` +* `streen_music` + +有关此数据集以及其他可能的数据集和计划的详细说明,我们建议读者访问 UrbanSound 网站,并查看创建者 J. Salamon,C。Jacoby 和 JP Bello 的这篇令人惊异的论文, *A Dataset 和城市声音研究分类法*( [http://www.justinsalamon.com/uploads/4/3/9/4/4394963/salamon_urbansound_acmmm14.pdf](http://www.justinsalamon.com/uploads/4/3/9/4/4394963/salamon_urbansound_acmmm14.pdf) ),22 nd ACM 国际多媒体国际会议,2014 年 11 月,美国奥兰多。我们感谢他们,以及纽约大学**城市科学与进步中心**( **CUSP** ), 现实。 + +要获取数据,您需要在其网站上填写表格,然后您将通过电子邮件获得下载链接。 解压缩文件后,您应该能够看到十个文件夹(十折)中的所有音频文件,以及一个包含有关数据集更多详细信息的`readme`文件。 + +# 探索性音频事件分析 + +我们将遵循标准的工作流程,对音频数据进行模型的分析,可视化,建模和评估。 下载完所有数据后,您会注意到总共有十个文件夹包含`WAV`格式的音频数据样本。 我们还有一个元数据文件夹,其中包含`UrbanSound8K.csv`文件中每个音频文件的元数据信息。 您可以使用此文件为每个文件分配类标签,也可以了解文件命名术语以进行相同的操作。 + +每个音频文件都以特定格式命名。 该名称采用`[fsID]-[classID]-[occurrenceID]-[sliceID].wav`格式,其格式如下: + +* `[fsID]`:从中摘录该片段(片段)的录音的自由声音 ID +* `[classID]`:声音类别的数字标识符 +* `[occurrenceID]`:数字标识符,用于区分原始录音中声音的不同出现 +* `[sliceID]`:数字标识符,用于区分同一事件中获取的不同片段 + +每个类标识符都是一个数字,可以映射到特定的类标签。 我们将在不久的将来对此进行更多的扩展。 让我们从对音频数据的一些基本探索性分析开始。 如果您想自己运行示例,可以从我们的 GitHub 存储库中引用`Exploratory Analysis Sound Data.ipynb` Jupyter Notebook。 + +首先,我们加载以下依赖项,包括`librosa`模块,如果没有该模块,则可能需要安装: + +```py +import glob +import os +import librosa +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.pyplot import specgram +import pandas as pd +import librosa.display +import IPython.display +import soundfile as sf + +%matplotlib inline +``` + +`librosa`模块是用于音频和音乐分析的出色的开源 Python 框架。 我们建议读者更详细地检查该框架。 在接下来的部分中,我们将使用它来分析音频数据并从中提取特征。 现在让我们加载一个数据文件夹进行分析: + +```py +files = glob.glob('UrbanSound8K/audio/fold1/*') +len(files) + +873 +``` + +我们可以看到每个文件夹大致包含 870 多个音频样本。 现在,基于`metadata`和`readme`文件的信息,我们可以创建一个类 ID,以名称映射音频样本类别: + +```py +class_map = {'0' : 'air_conditioner', '1' : 'car_horn', + '2' : 'children_playing', '3' : 'dog_bark', + '4' : 'drilling', '5' : 'engine_idling', + '6' : 'gun_shot', '7' : 'jackhammer', + '8' : 'siren', '9' : 'street_music'} +pd.DataFrame(sorted(list(class_map.items()))) +``` + +现在让我们从属于这些类别的每个类别中抽取十个不同的音频样本,以进行进一步分析: + +```py +samples = [(class_map[label], + [f for f in files if f.split('-')[1] == label][0]) + for label in class_map.keys()] +samples + +[('street_music', 'UrbanSound8K/audio/fold1\108041-9-0-11.wav'), + ('engine_idling', 'UrbanSound8K/audio/fold1\103258-5-0-0.wav'), + ('jackhammer', 'UrbanSound8K/audio/fold1\103074-7-0-0.wav'), + ('air_conditioner', 'UrbanSound8K/audio/fold1\127873-0-0-0.wav'), + ('drilling', 'UrbanSound8K/audio/fold1\14113-4-0-0.wav'), + ('children_playing', 'UrbanSound8K/audio/fold1\105415-2-0-1.wav'), + ('gun_shot', 'UrbanSound8K/audio/fold1\102305-6-0-0.wav'), + ('siren', 'UrbanSound8K/audio/fold1\106905-8-0-0.wav'), + ('car_horn', 'UrbanSound8K/audio/fold1\156194-1-0-0.wav'), + ('dog_bark', 'UrbanSound8K/audio/fold1\101415-3-0-2.wav')] +``` + +现在我们有了示例数据文件,在执行任何分析之前,我们仍然需要将音频数据读入内存。 我们注意到`librosa`对某些音频文件抛出了错误(因为它们的长度或采样率很短)。 因此,我们利用`soundfile` Python 框架读取音频文件,以获取其原始数据和原始采样率。 您可以在此处获取有关`soundfile`框架的更多信息: [https://pypi.org/project/SoundFile/](https://pypi.org/project/SoundFile/) 。 + +音频采样率定义为每秒传输的音频采样数,通常以 Hz 或 kHz(1 kHz 为 1,000 Hz)为单位。 `librosa`的默认采样率为 22,050 Hz,这是我们将重新采样所有音频数据以保持一致性的方式。 以下代码可帮助我们读取数据,并显示原始音频数据的总长度: + +```py +def get_sound_data(path, sr=22050): +data, fsr = sf.read(path) +data_22k = librosa.resample(data.T, fsr, sr) +if len(data_22k.shape) > 1: + data_22k = np.average(data_22k, axis=0) + return data_22k, sr + +sample_data = [(sample[0], get_sound_data(sample[1])) for sample in + samples] +[(sample[0], sample[1][0].shape) for sample in sample_data] + +``` + +```py +[('street_music', (88200,)), ('engine_idling', (88200,)), + ('jackhammer', (88200,)), ('air_conditioner', (44982,)), + ('drilling', (88200,)), ('children_playing', (88200,)), + ('gun_shot', (57551,)), ('siren', (88200,)), + ('car_horn', (5513,)), ('dog_bark', (88200,))] +``` + +很明显,大多数音频采样的持续时间约为四秒钟,但有些采样的持续时间却很短。 Jupyter 笔记本的魅力在于,您甚至可以将音频嵌入笔记本本身,并使用以下片段播放它。 + +对于`sample_data`中的数据: + +```py +print(data[0], ':') +IPython.display.display(IPython.display.Audio(data=data[1[0],rate=data[ 1][1])) +``` + +这将创建以下内容: + +![](img/dd8a372e-8efa-4671-bb94-4831617ad446.png) + +现在让我们通过绘制它们的波形来形象化这些不同的音频源的外观。 通常,这将是每个音频样本的波形幅度图: + +```py +i = 1 +fig = plt.figure(figsize=(15, 6)) +for item in sample_data: + plt.subplot(2, 5, i) + librosa.display.waveplot(item[1][0], sr=item[1][1], color='r', + alpha=0.7) + plt.title(item[0]) + i += 1 +plt.tight_layout() +``` + +创建的图将如下所示: + +![](img/a40c9f46-fc4a-4011-81c8-82581345bdbc.png) + +您可以在上图中清楚地看到不同的音频数据样本及其源标签和相应的音频波形图。 这描绘了一些有趣的见解。 **engine_idling** ,**手提凿岩机**和 **air_conditioner** 等音源通常具有恒定的声音,不会随时间变化。 因此,您可以注意到波形中的振幅恒定。 **警笛**和 **car_horn** 通常也具有恒定的音频波形,并具有间歇性的幅度增加。 **gun_shot** 通常在开始时会发出很大的声音,然后保持沉默。 **dog_bark** 间歇地进入。 因此,除了静音以外,声音还具有短的高振幅间隔。 您还能找到更多有趣的模式吗? + +音频数据的另一种有趣的可视化技术是声谱图。 通常,声谱图是一种视觉表示技术,用于表示音频数据中的频谱。 它们也被普遍称为**超声检查仪**和**语音图**。 让我们将音频样本可视化为频谱图: + +```py +i = 1 +fig = plt.figure(figsize=(15, 6)) + +for item in sample_data: + plt.subplot(2, 5, i) + specgram(item[1][0], Fs=item[1][1]) + plt.title(item[0]) + i += 1 +plt.tight_layout() +``` + +频谱图显示如下: + +![](img/c309828b-41bc-450c-a72a-af65ae9e4d49.png) + +我们可以看到如何用频谱图将音频数据表示为很好的图像表示形式,这对于像**卷积神经网络**( **CNN** )这样的模型很有用,因为 可以肯定地看到不同音频源在声谱图中存在明显差异。 但是,我们将使用梅尔谱图,它通常比基本谱图更好,因为它代表了梅尔标度的谱图。 名称 **mel** 来自单词 *melody* 。 这表明比例尺基于音高比较。 因此,梅尔音阶是对音高的感知尺度,听众已将其判断为彼此之间的距离相等。 如果我们使用 CNN 从这些频谱图中提取特征,这将非常有用。 以下代码段描绘了梅尔频谱图: + +```py +i = 1 +fig = plt.figure(figsize=(15, 6)) +for item in sample_data: + plt.subplot(2, 5, i) + S = librosa.feature.melspectrogram(item[1][0], sr=item[1] + [1],n_mels=128) + log_S = librosa.logamplitude(S) + librosa.display.specshow(log_S, sr=item[1][1], + x_axis='time',y_axis='mel') + plt.title(item[0]) + plt.colorbar(format='%+02.0f dB') + i += 1 +plt.tight_layout() +``` + +梅尔频谱图显示如下: + +![](img/3d3057b8-e22d-424c-8c47-ce16823d1d67.png) + +我们可以看到,借助梅尔音阶,可以更容易地根据音频源来区分频谱图。 现在,让我们集中讨论下一节中将用作特征工程基础资源的一些特定视觉技术。 首先,让我们看一下`gun_shot`音频样本作为梅尔频谱图的样子: + +```py +y = sample_data[6][1][0] +S = librosa.feature.melspectrogram(y, sr=22050, n_mels=128) +log_S = librosa.logamplitude(S) +plt.figure(figsize=(12,4)) +librosa.display.specshow(log_S, sr=22050, x_axis='time', y_axis='mel') +plt.colorbar(format='%+02.0f dB') +``` + +频谱图显示如下: + +![](img/ece5b21e-0d53-4e50-9b34-5249ab3debe7.png) + +频谱图与该音频源的音频波形图一致。 音频的另一个有趣方面是,通常任何音频时间序列数据都可以分解为谐波和打击乐成分。 这些可以呈现任何音频样本的全新有趣的表示形式。 让我们获取这些组件并将它们绘制成频谱图: + +```py +y_harmonic, y_percussive = librosa.effects.hpss(y) +S_harmonic = librosa.feature.melspectrogram(y_harmonic,sr=22050, + n_mels=128) +S_percussive = librosa.feature.melspectrogram(y_percussive,sr=22050) +log_Sh = librosa.power_to_db(S_harmonic) +log_Sp = librosa.power_to_db(S_percussive) + +# Make a new figure +plt.figure(figsize=(12,6)) +plt.subplot(2,1,1) +librosa.display.specshow(log_Sh, sr=sr, y_axis='mel') +plt.title('mel power spectrogram (Harmonic)') +plt.colorbar(format='%+02.0f dB') +plt.subplot(2,1,2) +librosa.display.specshow(log_Sp, sr=sr, x_axis='time', y_axis='mel') +plt.title('mel power spectrogram (Percussive)') +plt.colorbar(format='%+02.0f dB') +plt.tight_layout() +``` + +频谱图将显示如下: + +![](img/ed31ddc9-a4de-4ebe-8456-b06f50d3e897.png) + +您可以看到音频样本的两个不同成分显示为两个独特的声谱图,分别描述了谐波成分和打击乐成分。 + +音频数据的另一个非常有趣的描述是使用一个色谱图,该图显示了基于十二种不同音高类别(即{`C`,`C#`,`D`,`D#`,[ `E`,`F`,`F#`,`G`,`G#`,`A`,`A#`和`B`}。 这是用于描述音频信号随时间变化的各种音调强度的出色视觉工具。 通常,在构建色谱图之前,会对原始音频信号执行傅立叶变换或 Q 变换: + +```py +C = librosa.feature.chroma_cqt(y=y_harmonic, sr=sr) +# Make a new figure +plt.figure(figsize=(12, 4)) +# Display the chromagram: the energy in each chromatic pitch class +# as a function of time +librosa.display.specshow(C, sr=sr, x_axis='time', y_axis='chroma', + vmin=0, vmax=1) +plt.title('Chromagram') +plt.colorbar() +plt.tight_layout() +``` + +色谱图将显示如下: + +![](img/9cd9e2ed-8641-450d-90ec-204ea51f95bb.png) + +随着时间的推移,我们可以清楚地看到`gun_shot`音频样本的各种音调强度,这对于作为特征提取的基础图像肯定是有效的。 在下一节中,我们将使用其中一些技术进行特征提取。 + +# 功能工程和音频事件的表示 + +要构建可靠的分类模型,我们需要从原始音频数据中获得可靠且良好的特征表示。 我们将利用上一节中学到的一些技术进行要素工程。 如果您想自己运行示例,可以在`Feature Engineering.ipynb` Jupyter Notebook 中使用本节中使用的代码段。 我们将重用先前导入的所有库,并在此处利用`joblib`将功能保存到磁盘: + +```py +from sklearn.externals import joblib +``` + +接下来,我们将加载所有文件名,并定义一些实用程序函数以读取音频数据,还使我们能够获取音频子样本的窗口索引,我们将在短期内利用它们: + +```py +# get all file names +ROOT_DIR = 'UrbanSound8K/audio/' +files = glob.glob(ROOT_DIR+'/**/*') + +# load raw audio data +def get_sound_data(path, sr=22050): + data, fsr = sf.read(path) + data_resample = librosa.resample(data.T, fsr, sr) + if len(data_resample.shape) > 1: + data_resample = np.average(data_resample, axis=0) + return data_resample, sr + +# function to get start and end indices for audio sub-sample +def windows(data, window_size): + start = 0 + while start < len(data): + yield int(start), int(start + window_size) + start += (window_size / 2) +``` + +我们将遵循的特征工程策略有些复杂,但是我们将在此处尝试以简洁的方式对其进行说明。 我们已经看到我们的音频数据样本的长度不同。 但是,如果我们要构建一个强大的分类器,则每个样本的功能必须保持一致。 因此,我们将从每个音频文件中提取(固定长度的)音频子样本,并从每个这些子样本中提取特征。 + +我们将总共使用三种特征工程技术来构建三个特征表示图,这最终将为我们的每个音频子样本提供一个三维图像特征图。 下图描述了我们将采用的工作流程: + +![](img/70041e60-68d3-4ebf-900d-20bf3d0972b4.png) + +这个想法来自 Karol J. Piczak 的出色论文*,* *具有卷积神经网络的环境声音分类*( [https://ieeexplore.ieee.org/document/ 7324337 /](https://ieeexplore.ieee.org/document/7324337/) ),IEEE2015。他将梅尔频谱图用于一般必要的特征,CNN 可以使用这些特征来进行特征提取。 但是,我们已经考虑了对最终特征图的一些其他转换。 + +第一步是将帧(列)的总数定义为 **64** ,将波段(行)的总数定义为 **64** ,这形成了每个特征图的尺寸(64 x 64)。 然后,基于此,我们提取音频数据的窗口,从每个音频数据样本中形成子样本。 + +考虑每个音频子样本,我们首先创建一个梅尔声谱图。 由此,我们创建了一个对数缩放的梅尔频谱图,作为特征图之一,音频子样本的谐波分量和敲击分量的平均特征图(再次对数缩放)以及对数缩放的增量或导数 梅尔频谱图作为第三特征图。 这些特征图的每一个都可以表示为 64 x 64 图像,并且通过组合它们,我们可以为每个音频子样本获得尺寸为(64、64、3)的 3-D 特征图。 现在,为该工作流程定义函数: + +```py +def extract_features(file_names, bands=64, frames=64): + window_size = 512 * (frames - 1) + log_specgrams_full = [] + log_specgrams_hp = [] + class_labels = [] + + # for each audio sample + for fn in file_names: + file_name = fn.split('\')[-1] + class_label = file_name.split('-')[1] + sound_data, sr = get_sound_data(fn, sr=22050) + + # for each audio signal sub-sample window of data + for (start,end) in windows(sound_data, window_size): + if(len(sound_data[start:end]) == window_size): + signal = sound_data[start:end] + + # get the log-scaled mel-spectrogram + melspec_full = librosa.feature.melspectrogram(signal, + n_mels = + bands) + logspec_full = librosa.logamplitude(melspec_full) + logspec_full = logspec_full.T.flatten()[:,np.newaxis].T + + # get the log-scaled, averaged values for the + # harmonic and percussive components + y_harmonic, y_percussive =librosa.effects.hpss(signal) + melspec_harmonic = + librosa.feature.melspectrogram(y_harmonic, + n_mels=bands) + melspec_percussive = + librosa.feature.melspectrogram(y_percussive, + n_mels=bands) + logspec_harmonic = + librosa.logamplitude(melspec_harmonic) + logspec_percussive = + librosa.logamplitude(melspec_percussive) + logspec_harmonic = logspec_harmonic.T.flatten()[:, + np.newaxis].T + logspec_percussive = logspec_percussive.T.flatten()[:, + np.newaxis].T + logspec_hp = np.average([logspec_harmonic, + logspec_percussive], + axis=0) + log_specgrams_full.append(logspec_full) + log_specgrams_hp.append(logspec_hp) + class_labels.append(class_label) + + # create the first two feature maps + log_specgrams_full = np.asarray(log_specgrams_full).reshape( + len(log_specgrams_full), bands, + frames, 1) + log_specgrams_hp = np.asarray(log_specgrams_hp).reshape( + len(log_specgrams_hp), bands, + frames, 1) + features = np.concatenate((log_specgrams_full, + log_specgrams_hp, + np.zeros(np.shape( + log_specgrams_full))), + axis=3) + + # create the third feature map which is the delta (derivative) + # of the log-scaled mel-spectrogram + for i in range(len(features)): + features[i, :, :, 2] = librosa.feature.delta(features[i, + :, :, 0]) + return np.array(features), np.array(class_labels, dtype = np.int) +``` + +现在我们准备使用此功能。 我们将在前面的工作流程中讨论的策略基础上,将其用于所有 8,732 音频样本,以从该数据的许多子样本中创建特征图。 + +```py +features, labels = extract_features(files) +features.shape, labels.shape +((30500, 64, 64, 3), (30500,)) +``` + +我们从 8,732 个音频数据文件中总共获得了 30,500 个特征图。 这非常好,并且正如我们前面所讨论的,每个特征图都是尺寸(64、64、3)。 现在,基于以下 30,500 个数据点,查看音频源的整体类表示形式: + +```py +from collections import Counter +Counter(labels) +Counter({0: 3993, 1: 913, 2: 3947, 3: 2912, 4: 3405, + 5: 3910, 6: 336, 7: 3473, 8: 3611, 9: 4000}) +``` + +我们可以看到,不同类别中数据点的总体分布是相当均匀和适当的。 对于诸如 1(`car_horn`)和 6(`gun_shot`)的某些类别,表示与其他类别相比非常低; 这是可以预期的,因为这些类别的音频数据持续时间通常比其他类别要短得多。 现在让我们继续可视化这些功能图: + +```py +class_map = {'0' : 'air_conditioner', '1' : 'car_horn', '2' : + 'children_playing','3' : 'dog_bark', '4' : 'drilling','5' : + 'engine_idling','6' : 'gun_shot', '7' : 'jackhammer', '8' : + 'siren', '9' : 'street_music'} +categories = list(set(labels)) +sample_idxs = [np.where(labels == label_id)[0][0] for label_id in + categories] +feature_samples = features[sample_idxs] + +plt.figure(figsize=(16, 4)) +for index, (feature_map, category) in enumerate(zip(feature_samples, + categories)): + plt.subplot(2, 5, index+1) + plt.imshow(np.concatenate((feature_map[:,:,0], + feature_map[:,:,1], + feature_map[:,:,2]), + axis=1), + cmap='viridis') + plt.title(class_map[str(category)]) +plt.tight_layout() +t = plt.suptitle('Visualizing Feature Maps for Audio Clips') +``` + +功能图将显示如下: + +![](img/14342019-e837-4817-89d5-351de82d81f8.png) + +上图向我们展示了每个音频类别的一些示例特征图看起来是什么样的,并且显而易见的是,每个特征图都是三维图像。 现在,我们将这些基本功能保存到磁盘: + +```py +joblib.dump(features, 'base_features.pkl') +joblib.dump(labels, 'dataset_labels.pkl') +``` + +这些基本功能将作为下一部分进一步功能设计的起点,在此我们将释放转移学习的真正力量。 + +# 带有转移学习的音频事件分类 + +现在,我们准备开始构建音频事件分类器。 我们有基本的特征图,但仍然需要做更多的特征工程。 您始终可以从头开始构建 CNN 以摄取这些图像,然后将其连接到完全连接的深**多层感知器**( **MLP** )来构建分类器。 但是,在这里,我们将通过使用一种预训练的模型进行特征提取来利用转移学习的力量。 更具体地说,我们将使用 VGG-16 模型作为特征提取器,然后在这些特征上训练完全连接的深度网络。 + +# 从基本要素构建数据集 + +第一步是加载基本功能,并创建训练,验证和测试数据集。 为此,我们需要从磁盘加载基本功能和标签: + +```py +features = joblib.load('base_features.pkl') +labels = joblib.load('dataset_labels.pkl') +data = np.array(list(zip(features, labels))) +features.shape, labels.shape + +((30500, 64, 64, 3), (30500,)) +``` + +现在,我们将随机整理数据并创建训练,验证和测试数据集: + +```py +np.random.shuffle(data) +train, validate, test = np.split(data, [int(.6*len(data)),int(.8*len(data))]) +train.shape, validate.shape, test.shape + +((18300, 2), (6100, 2), (6100, 2)) +``` + +最后,我们还可以使用以下代码段检查每个数据集中的每类分布: + +```py +print('Train:', Counter(item[1] for item in train),'nValidate:', Counter(item[1] for item in validate),'nTest:',Counter(item[1] for item + in test)) + +Train: Counter({9: 2448, 2: 2423, 0: 2378, 5: 2366, 8: 2140, + 7: 2033, 4: 2020, 3: 1753, 1: 542, 6: 197}) +Validate: Counter({0: 802, 5: 799, 2: 774, 9: 744, 8: 721, + 7: 705, 4: 688, 3: 616, 1: 183, 6: 68}) +Test: Counter({0: 813, 9: 808, 2: 750, 8: 750, 5: 745, 7: 735, + 4: 697, 3: 543, 1: 188, 6: 71}) +``` + +因此,我们可以看到整个数据集中每个类的数据点一致且均匀地分布。 + +# 转移学习以进行特征提取 + +现在来了有趣的部分。 我们准备利用转移学习从基本要素地图图像中为每个数据点提取有用的要素。 为此,我们将使用出色的预训练深度学习模型,该模型已被证明是图像上非常有效的特征提取器。 我们将在这里使用 VGG-16 模型。 但是,我们将在这里使用它作为简单的特征提取器,而无需进行任何微调(这是我们在前几章中探讨的内容)。 + +随意利用微调,这甚至可以带来更好的分类器。 我们首先定义一些基本的实用程序和函数来处理基本图像: + +```py +from keras.preprocessing import image +from keras.applications.imagenet_utils import preprocess_input +from PIL import Image +def process_sound_data(data): + data = np.expand_dims(data, axis=0) + data = preprocess_input(data) + return data +``` + +现在,我们将加载 VGG-16 模型,但仅作为特征提取器。 因此,我们最终将不会使用其密集层: + +```py +from keras.applications import vgg16 +from keras.models import Model +import keras +vgg = vgg16.VGG16(include_top=False, weights='imagenet',input_shape= + (64, 64, 3)) +output = vgg.layers[-1].output +output = keras.layers.Flatten()(output) +model = Model(vgg.input, output) +model.trainable = False +model.summary() + +_________________________________________________________________ +Layer (type) Output Shape Param # +================================================================= +input_2 (InputLayer) (None, 64, 64, 3) 0 +_________________________________________________________________ +block1_conv1 (Conv2D) (None, 64, 64, 64) 1792 +_________________________________________________________________ +block1_conv2 (Conv2D) (None, 64, 64, 64) 36928 +_________________________________________________________________ +... +... +_________________________________________________________________ +block5_conv3 (Conv2D) (None, 4, 4, 512) 2359808 +_________________________________________________________________ +block5_pool (MaxPooling2D) (None, 2, 2, 512) 0 +_________________________________________________________________ +flatten_2 (Flatten) (None, 2048) 0 +================================================================= +Total params: 14,714,688 +Trainable params: 0 +Non-trainable params: 14,714,688 +_________________________________________________________________ +``` + +从前面的模型摘要中可以明显看出,我们输入的基本特征图图像的尺寸为`(64, 64, 3)`,从中我们最终将得到大小为 2,048 的一维特征向量。 让我们构建一个通用函数,以帮助我们利用转移学习并获得这些功能,这些功能通常被称为**瓶颈功能**: + +```py +def extract_tl_features(model, base_feature_data): + dataset_tl_features = [] + for index, feature_data in enumerate(base_feature_data): + if (index+1) % 1000 == 0: + print('Finished processing', index+1, 'sound feature maps') + pr_data = process_sound_data(feature_data) + tl_features = model.predict(pr_data) + tl_features = np.reshape(tl_features, + tl_features.shape[1]) + dataset_tl_features.append(tl_features) + return np.array(dataset_tl_features) +``` + +现在可以将此功能与我们的 VGG-16 模型一起使用,以从我们的每个音频子样本基本特征图图像中提取有用的特征。 我们将对所有数据集执行此操作: + +```py +# extract train dataset features +train_base_features = [item[0] for item in train] +train_labels = np.array([item[1] for item in train]) +train_tl_features = extract_tl_features(model=model, + base_feature_data=train_base_features) + +# extract validation dataset features +validate_base_features = [item[0] for item in validate] +validate_labels = np.array([item[1] for item in validate]) +validate_tl_features = extract_tl_features(model=model, + base_feature_data=validate_base_features) + +# extract test dataset features +test_base_features = [item[0] for item in test] +test_labels = np.array([item[1] for item in test]) +test_tl_features = extract_tl_features(model=model, + base_feature_data=test_base_features) + +train_tl_features.shape, validate_tl_features.shape, test_tl_features.shape + +((18300, 2048), (6100, 2048), (6100, 2048)) +``` + +现在,我们可以将这些功能和标签保存到磁盘上,以便以后可以随时用于构建分类器,而不必依赖于始终保持笔记本计算机处于打开状态: + +```py +joblib.dump(train_tl_features, 'train_tl_features.pkl') +joblib.dump(train_labels, 'train_labels.pkl') +joblib.dump(validate_tl_features, 'validate_tl_features.pkl') +joblib.dump(validate_labels, 'validate_labels.pkl') +joblib.dump(test_tl_features, 'test_tl_features.pkl') +joblib.dump(test_labels, 'test_labels.pkl') +``` + +# 建立分类模型 + +现在,我们准备在上一节中提取的功能上构建分类模型。 如果您想自己运行示例,可以在`Modeling.ipynb` Jupyter Notebook 中使用此部分的代码。 首先,让我们加载一些基本的依赖项: + +```py +from sklearn.externals import joblib +import keras +from keras import models +from keras import layers +import model_evaluation_utils as meu +import matplotlib.pyplot as plt + +%matplotlib inline +``` + +我们将使用名为`model_evaluation_utils`的漂亮模型评估实用程序模块来评估我们的分类器并稍后测试其性能。 现在让我们加载功能集和数据点类标签: + +```py +train_features = joblib.load('train_tl_features.pkl') +train_labels = joblib.load('train_labels.pkl') +validation_features = joblib.load('validate_tl_features.pkl') +validation_labels = joblib.load('validate_labels.pkl') +test_features = joblib.load('test_tl_features.pkl') +test_labels = joblib.load('test_labels.pkl') +train_features.shape, validation_features.shape, test_features.shape + +((18300, 2048), (6100, 2048), (6100, 2048)) + +train_labels.shape, validation_labels.shape, test_labels.shape + +((18300,), (6100,), (6100,)) +``` + +因此,我们可以看到我们所有的功能集和相应的标签均已加载。 输入要素集是从上一节中使用的 VGG-16 模型获得的大小为 2,048 的一维向量。 现在,我们需要对分类类标签进行一次热编码,然后才能将其输入到深度学习模型中。 以下代码段可帮助我们实现这一目标: + +```py +from keras.utils import to_categorical +train_labels_ohe = to_categorical(train_labels) +validation_labels_ohe = to_categorical(validation_labels) +test_labels_ohe = to_categorical(test_labels) +train_labels_ohe.shape, validation_labels_ohe.shape, test_labels_ohe.shape + +((18300, 10), (6100, 10), (6100, 10)) +``` + +现在,我们将使用具有四个隐藏层的完全连接的网络来构建深度学习分类器。 我们将使用常见的组件(如辍学)来防止过度拟合,并使用模型的 Adam 优化器。 以下代码描述了模型体系结构的详细信息: + +```py +model = models.Sequential() +model.add(layers.Dense(1024, activation='relu', + input_shape=(train_features.shape[1],))) +model.add(layers.Dropout(0.4)) +model.add(layers.Dense(1024, activation='relu')) +model.add(layers.Dropout(0.4)) +model.add(layers.Dense(512, activation='relu')) +model.add(layers.Dropout(0.5)) +model.add(layers.Dense(512, activation='relu')) +model.add(layers.Dropout(0.5)) +model.add(layers.Dense(train_labels_ohe.shape[1],activation='softmax')) +model.compile(loss='categorical_crossentropy', + optimizer='adam',metrics=['accuracy']) +model.summary() + +_________________________________________________________________ +Layer (type) Output Shape Param # +================================================================= +dense_1 (Dense) (None, 1024) 2098176 +_________________________________________________________________ +dropout_1 (Dropout) (None, 1024) 0 +_________________________________________________________________ +dense_2 (Dense) (None, 1024) 1049600 +_________________________________________________________________ +dropout_2 (Dropout) (None, 1024) 0 +_________________________________________________________________ +dense_3 (Dense) (None, 512) 524800 +_________________________________________________________________ +dropout_3 (Dropout) (None, 512) 0 +_________________________________________________________________ +dense_4 (Dense) (None, 512) 262656 +_________________________________________________________________ +dropout_4 (Dropout) (None, 512) 0 +_________________________________________________________________ +dense_5 (Dense) (None, 10) 5130 +================================================================= + +Total params: 3,940,362 +Trainable params: 3,940,362 +Non-trainable params: 0 +``` + +然后,在 AWS **p2.x** 实例上对该模型进行了约 50 个纪元的训练,批处理大小为 128。 您可以尝试使用时间和批处理大小来获得可靠的模型,如下所示: + +```py +history = model.fit(train_features, train_labels_ohe,epochs=50, + batch_size=128, + validation_data=(validation_features, + validation_labels_ohe),shuffle=True, verbose=1) +Train on 18300 samples, validate on 6100 samples +Epoch 1/50 +18300/18300 - 2s - loss: 2.7953 - acc: 0.3959 - val_loss: 1.0665 - val_acc: 0.6675 +Epoch 2/50 +18300/18300 - 1s - loss: 1.1606 - acc: 0.6211 - val_loss: 0.8179 - val_acc: 0.7444 +... +... +Epoch 48/50 +18300/18300 - 1s - loss: 0.2753 - acc: 0.9157 - val_loss: 0.4218 - val_acc: 0.8797 +Epoch 49/50 +18300/18300 - 1s - loss: 0.2813 - acc: 0.9142 - val_loss: 0.4220 - val_acc: 0.8810 +Epoch 50/50 +18300/18300 - 1s - loss: 0.2631 - acc: 0.9197 - val_loss: 0.3887 - val_acc: 0.8890 +``` + +我们获得的验证准确度接近 **89%**,这非常好,看起来很有希望。 我们还可以绘制模型的整体精度图和损耗图,以更好地了解事物的外观,如下所示: + +```py +f, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4)) +t = f.suptitle('Deep Neural Net Performance', fontsize=12) +f.subplots_adjust(top=0.85, wspace=0.2) +epochs = list(range(1,51)) +ax1.plot(epochs, history.history['acc'], label='Train Accuracy') +ax1.plot(epochs, history.history['val_acc'], label='Validation Accuracy') +ax1.set_ylabel('Accuracy Value') +ax1.set_xlabel('Epoch') +ax1.set_title('Accuracy') +l1 = ax1.legend(loc="best") +ax2.plot(epochs, history.history['loss'], label='Train Loss') +ax2.plot(epochs, history.history['val_loss'], label='Validation Loss') +ax2.set_ylabel('Loss Value') +ax2.set_xlabel('Epoch') +ax2.set_title('Loss') +l2 = ax2.legend(loc="best") +``` + +这将创建以下图: + +![](img/a164d6a2-685a-4ad5-b1fe-3af618612e17.png) + +我们可以看到模型在训练和验证之间的损失和准确性是相当一致的。 也许略有过度拟合,但考虑到它们之间的差异很小,可以忽略不计。 + +# 评估分类器性能 + +从字面上看,现在该对我们的模型进行测试了。 我们将使用测试数据集对模型进行预测,然后根据基本事实标签对它们进行评估。 为此,我们首先需要使用以下代码片段获取测试数据对模型的预测,并从数字标签到实际文本标签进行反向映射: + +```py +predictions = model.predict_classes(test_features) +class_map = {'0' : 'air_conditioner', '1' : 'car_horn', + '2' : 'children_playing', '3' : 'dog_bark', + '4' : 'drilling', '5' : 'engine_idling', + '6' : 'gun_shot', '7' : 'jackhammer', + '8' : 'siren', '9' : 'street_music'} +test_labels_categories = [class_map[str(label)]for label in + test_labels] +prediction_labels_categories = [class_map[str(label)]for label in + predictions] category_names = list(class_map.values()) +``` + +现在让我们使用`model_evaluation_utils`模块来根据测试数据评估模型的性能。 我们首先获得总体性能指标: + +```py +meu.get_metrics(true_labels=test_labels_categories, + predicted_labels=prediction_labels_categories) + +Accuracy: 0.8869 +Precision: 0.8864 +Recall: 0.8869 +F1 Score: 0.8861 +``` + +我们获得了总体模型准确性,并且`f1-score`接近 89%,这非常好,并且与我们从验证数据集中获得的一致。 接下来让我们看一下每类模型的性能: + +```py +meu.display_classification_report(true_labels=test_labels_categories, + predicted_labels=prediction_labels_categories, + classes=category_names) + + precision recall f1-score support + + car_horn 0.87 0.73 0.79 188 + siren 0.95 0.94 0.94 750 + drilling 0.88 0.93 0.90 697 + gun_shot 0.94 0.94 0.94 71 +children_playing 0.83 0.79 0.81 750 + air_conditioner 0.89 0.94 0.92 813 + jackhammer 0.92 0.93 0.92 735 + engine_idling 0.94 0.95 0.95 745 + dog_bark 0.87 0.83 0.85 543 + street_music 0.81 0.81 0.81 808 + + avg / total 0.89 0.89 0.89 6100 +``` + +这使我们可以更清楚地了解模型确实运行良好以及可能遇到问题的确切类。 大多数类似乎运行良好,尤其是设备声音,例如`gun_shot`,`jackhammer`和`engine_idling`等。 似乎`street_music`和`children_playing`最麻烦。 + +混淆矩阵可以帮助我们了解最可能发生错误分类的地方,并帮助我们更好地理解这一点: + +```py +meu.display_confusion_matrix_pretty(true_labels=test_labels_categories, + predicted_labels=prediction_labels_categories, + classes=category_names) +``` + +矩阵将显示如下: + +![](img/ff28a958-6bcb-4ae9-84f8-8300bffd4aa7.png) + +从矩阵的对角线看,我们可以看到大多数模型预测都是正确的,这非常好。 关于错误分类,我们可以看到,属于`street_music`,`dog_bark`和`children_playing`的许多样本彼此之间都被错误分类了,考虑到所有这些事件都是在公开场合和外部发生的,这是一种预期 他们有可能一起发生。 对于`drilling`和`jackhammer`也是一样。 幸运的是,`gun_shot`和`children_playing`之间的错误分类几乎没有重叠。 + +因此,在这个复杂的案例研究中,我们可以看到有效的转移学习是如何工作的,在该案例中,我们利用图像分类器帮助我们构建了强大而有效的音频事件分类器。 现在,我们可以使用以下代码保存此模型以供将来使用: + +```py +model.save('sound_classification_model.h5') +``` + +您现在可能会认为这很好。 但是,我们在静态数据集上进行了所有操作。 我们将如何在现实世界中使用此模型进行音频事件识别和分类? 我们将在下一节中讨论策略。 + +# 构建深度学习音频事件标识符 + +现在,我们将研究一种策略,利用该策略,我们可以利用上一节中构建的分类模型来构建实际的音频事件标识符。 这将使我们能够利用本章中定义的整个工作流程来获取任何新的音频文件,并预测该文件可能属于的类别,从构建基本特征图开始,使用 VGG-16 模型提取特征,然后 利用我们的分类模型做出预测。 如果您想自己运行示例,可以在`Prediction Pipeline.ipynb` Jupyter Notebook 中使用本节中使用的代码段。 笔记本包含`AudioIdentifier`类,该类是通过重用本章前面各节中构建的所有组件而创建的。 请参阅笔记本以访问该类的完整代码,因为我们将更加关注实际的预测管道,以使内容更加简洁。 我们将通过为类的实例提供分类模型的路径来初始化它: + +```py +ai = + AudioIdentifier(prediction_model_path='sound_classification_model.h5') +``` + +现在,我们已经下载了十个音频类别中的三个的三个全新的音频数据文件。 让我们加载它们,以便我们可以在它们上测试模型的性能: + +```py +siren_path = 'UrbanSound8K/test/sirenpolice.wav' +gunshot_path = 'UrbanSound8K/test/gunfight.wav' +dogbark_path = 'UrbanSound8K/test/dog_bark.wav' +siren_audio, siren_sr = ai.get_sound_data(siren_path) +gunshot_audio, gunshot_sr = ai.get_sound_data(gunshot_path) +dogbark_audio, dogbark_sr = ai.get_sound_data(dogbark_path) +actual_sounds = ['siren', 'gun_shot', 'dog_bark'] +sound_data = [siren_audio, gunshot_audio, dogbark_audio] +sound_rate = [siren_sr, gunshot_sr, dogbark_sr] +sound_paths = [siren_path, gunshot_path, dogbark_path] +``` + +让我们可视化这三个音频文件的波形,并了解它们的结构: + +```py +i = 1 +fig = plt.figure(figsize=(12, 3.5)) +t = plt.suptitle('Visualizing Amplitude Waveforms for Audio Clips', + fontsize=14) +fig.subplots_adjust(top=0.8, wspace=0.2) + +for sound_class, data, sr in zip(actual_sounds, sound_data,sound_rate): + plt.subplot(1, 3, i) + librosa.display.waveplot(data, sr=sr, color='r', alpha=0.7) + plt.title(sound_class) + i += 1 +plt.tight_layout(pad=2.5) +``` + +可视化效果如下所示: + +![](img/76c3a773-50a7-4a3a-a94e-82ffc1fbc73f.png) + +基于可视化,基于音频源,它们似乎是一致的,到目前为止,我们的管道运行良好。 现在,我们为这些音频文件提取基本特征图: + +```py +siren_feature_map = ai.extract_base_features(siren_audio)[0] +gunshot_feature_map = ai.extract_base_features(gunshot_audio)[0] +dogbark_feature_map = ai.extract_base_features(dogbark_audio)[0] +feature_maps = [siren_feature_map, gunshot_feature_map,dogbark_feature_map] +plt.figure(figsize=(14, 3)) +t = plt.suptitle('Visualizing Feature Maps for Audio + Clips',fontsize=14) +fig.subplots_adjust(top=0.8, wspace=0.1) + +for index, (feature_map, category) in + enumerate(zip(feature_maps,actual_sounds)): + plt.subplot(1, 3, index+1) + plt.imshow(np.concatenate((feature_map[:,:,0], + feature_map[:,:,1], + feature_map[:,:,2]), axis=1), + cmap='viridis') +plt.title(category) +plt.tight_layout(pad=1.5) +``` + +功能图将显示如下: + +![](img/2b20f0eb-7c5b-42d0-ba6d-a6be88b58247.png) + +根据我们在训练阶段观察到的图像,图像特征图看起来非常一致。 现在,我们可以利用我们的预测管道来预测每种声音的音频源类别: + +```py +predictions = + [ai.prediction_pipeline(audiofile_path,return_class_label=True) + for audiofile_path in sound_paths] +result_df = pd.DataFrame({'Actual Sound': actual_sounds, + 'Predicted Sound': predictions, + 'Location': sound_paths}) +result_df +``` + +我们得出以下预测: + +![](img/c409633e-d70e-481a-b49c-da5e05090126.png) + +看起来我们的模型能够正确识别所有这些音频样本。 我们鼓励您检查笔记本中的`AudioIdentifier`类,以了解我们如何在后台实现预测管道。 我们利用了在本章中学到的所有概念来构建此管道。 + +# 摘要 + +在本章中,我们研究了一个全新的问题和案例研究,涉及音频识别和分类。 涵盖了围绕音频数据和信号的概念,包括可视化和理解此数据类型的有效技术。 + +我们还研究了有效的特征工程技术,以及如何使用转移学习从音频数据的图像表示中提取有效特征。 这向我们展示了转移学习的希望,以及如何利用知识从一个领域(图像)转移到另一个领域(音频),并建立一个非常强大且有效的分类器。 最后,我们建立了一个完整的端到端管道,用于识别和分类音频数据的新样本。 请在网络上进一步检查带注释的音频的数据集,看看是否可以利用从此处学习的迁移学习中获得的概念来构建更大,更好的音频标识符和分类器。 请继续关注有关转移学习的更多有趣示例和案例研究。 \ No newline at end of file diff --git a/docs/handson-tl-py/9.md b/docs/handson-tl-py/9.md new file mode 100644 index 0000000000000000000000000000000000000000..7fe3d790fda8dada6e2c92ba9cc6bd0cc634e198 --- /dev/null +++ b/docs/handson-tl-py/9.md @@ -0,0 +1,757 @@ +# 深梦 + +本章重点介绍了生成型深度学习的领域,这已成为真正的**人工智能**( **AI** )最前沿的核心思想之一。 我们将关注**卷积神经网络**( **CNN** )如何利用转移学习来思考或可视化图像中的图案。 它们可以生成描述这些卷积网络思维甚至梦想方式之前从未见过的图像模式! DeepDream 于 2015 年由 Google 首次发布,由于深层网络开始从图像生成有趣的图案,因此引起了轰动。 本章将涵盖以下主要主题: + +* 动机—心理轻瘫 +* 计算机视觉中的算法异同 +* 通过可视化 CNN 的内部层来了解 CNN 所学的知识 +* DeepDream 算法以及如何创建自己的梦想 + +就像前面的章节一样,我们将结合使用概念知识和直观的实际操作示例。 您可以在 GitHub 存储库中的 [https://github.com/dipanjanS/hands-on-transfer-learning-with-python](https://github.com/dipanjanS/hands-on-transfer-learning-with-python) 的 GitHub 存储库的`Chapter 9`文件夹中快速阅读本章的代码。 可以根据需要参考本章。 + +# 介绍 + +在详细介绍神经 DeepDream 之前,让我们看一下人类所经历的类似行为。 您是否曾经尝试过寻找云中的形状,电视机中的抖动和嘈杂信号,甚至看过一张被烤面包烤成的面孔? + +Pareidolia 是一种心理现象,使我们看到随机刺激中的模式。 人类倾向于感知实际上不存在的面孔或样式的趋势。 这通常导致将人的特征分配给对象。 请注意,看到不存在的模式(假阳性)相对于看不到存在的模式(假阴性)对进化结果的重要性。 例如,看到没有狮子的狮子很少会致命。 但是,没有看到有一只的掠食性狮子,那当然是致命的。 + +pareidolia 的神经学基础主要位于大脑深处的大脑颞叶区域,称为**梭状回(HTG1),在此区域,人类和其他动物的神经元专用于识别面部和其他物体。** + +# 计算机视觉中的算法异同 + +计算机视觉的主要任务之一是特别是对象检测和面部检测。 有许多具有面部检测功能的电子设备在后台运行此类算法并检测面部。 那么,当我们在这些软件的前面放置诱发 Pareidolia 的物体时会发生什么呢? 有时,这些软件解释面孔的方式与我们完全相同。 有时它可能与我们一致,有时它会引起我们全新的面貌。 + +在使用人工神经网络构建的对象识别系统的情况下,更高级别的特征/层对应于更易识别的特征,例如面部或物体。 增强这些功能可以带出计算机的视觉效果。 这些反映了网络以前看到的训练图像集。 让我们以 Inception 网络为例,让它预测一些诱发 Pareidolia 的图像中看到的物体。 让我们在下面的照片中拍摄这些三色堇花。 对我而言,这些花有时看起来像蝴蝶,有时又像愤怒的人,留着浓密的胡须的脸: + +![](img/cb28331b-c874-4ffc-bb59-a54e9a855452.jpg) + +让我们看看 Inception 模型在其中的表现。 我们将使用在 ImageNet 数据上训练的预训练的 Inception 网络模型。 要加载模型,请使用以下代码: + +```py +from keras.applications import inception_v3 +from keras import backend as K +from keras.applications.imagenet_utils import decode_predictions +from keras.preprocessing import image +K.set_learning_phase(0) + +model = inception_v3.InceptionV3(weights='imagenet',include_top=True) +``` + +要读取图像文件并将其转换为一个图像的数据批,这是 Inception 网络模型的`predict`功能的预期输入,我们使用以下功能: + +```py +def preprocess_image(image_path): + img = image.load_img(image_path) + img = image.img_to_array(img) + #convert single image to a batch with 1 image + img = np.expand_dims(img, axis=0) + img = inception_v3.preprocess_input(img) + return img +``` + +现在,让我们使用前面的方法预处理输入图像并预测模型看到的对象。 我们将使用`modeld.predict`方法来获取 ImageNet 中所有 1,000 个类的预测类概率。 要将此概率数组转换为按概率得分的降序排列的实类标签,我们使用`keras`中的`decode_predictions`方法。 可在此处找到所有 1,000 个 ImageNet 类或同义词集的列表: [http://image-net.org/challenges/LSVRC/2014/browse-synsets](http://image-net.org/challenges/LSVRC/2014/browse-synsets) 。 请注意,三色堇花不在训练模型的已知类集中: + +```py +img = preprocess_image(base_image_path) +preds = model.predict(img) +for n, label, prob in decode_predictions(preds)[0]: + print (label, prob) +``` + +的预测。 最高预测的类别都不具有很大的概率,这是可以预期的,因为模型之前没有看到过这种特殊的花朵: + +```py +bee 0.022255851 +earthstar 0.018780833 +sulphur_butterfly 0.015787734 +daisy 0.013633176 +cabbage_butterfly 0.012270376 +``` + +在上一张照片中,模型找到**蜜蜂**。 好吧,这不是一个不好的猜测。 如您所见,在黄色的花朵中,中间的黑色/棕色阴影的下半部分确实像蜜蜂。 此外,它还会看到一些黄色和白色的蝴蝶,如**硫**和**卷心菜**蝴蝶,就像我们人类一眼就能看到的。 下图显示了这些已识别对象/类的实际图像。 显然,此输入激活了该网络中的某些特征检测器隐藏层。 也许检测昆虫/鸟类翅膀的过滤器与一些与颜色相关的过滤器一起被激活,以得出上述结论: + +![](img/8aafabe1-dc15-4785-a4ee-2d0abb44b08c.png) + +ImageNet 体系结构及其中的功能图数量很多。 让我们假设一下,我们知道可以检测这些机翼的要素地图层。 现在,给定输入图像,我们可以从这一层提取要素。 我们可以更改输入图像,以使来自该层的激活增加吗? 这意味着我们必须修改输入图像,以便在输入图像中看到更多类似机翼的物体,即使它们不在那里。 最终的图像将像梦一样,到处都是蝴蝶。 这正是 DeepDream 中完成的工作。 + +现在,让我们看一下 Inception 网络中的​​一些功能图。 要了解卷积模型学到的知识,我们可以尝试可视化卷积过滤器。 + +# 可视化特征图 + +可视化 CNN 模型涉及在给定一定输入的情况下,查看网络中各种卷积和池化层输出的中间层特征图。 这样就可以了解网络如何处理输入以及如何分层提取各种图像特征。 所有要素图都具有三个维度:宽度,高度和深度(通道)。 我们将尝试将它们可视化为 InceptionV3 模型。 + +让我们为拉布拉多犬拍摄以下照片,并尝试形象化各种特征图。 由于 InceptionV3 模型具有很深的深度,因此我们将仅可视化一些层: + +![](img/5a131554-b82b-40b6-8406-a0f8e1f1da7b.jpg) + +首先,让我们创建一个模型以获取输入图像并输出所有内部激活层。 InceptionV3 中的激活层称为`activation_i`。 因此,我们可以从加载的 Inception 模型中过滤掉激活层,如以下代码所示: + +```py +activation_layers = [ layer.output for layer in model.layers if + layer.name.startswith("activation_")] + +layer_names = [ layer.name for layer in model.layers if + layer.name.startswith("activation_")] +``` + +现在,让我们创建一个模型,该模型获取输入图像并将所有上述激活层特征作为列表输出,如以下代码所示: + +```py +from keras.models import Model +activation_model = Model(inputs=model.input, outputs=activation_layers) +``` + +现在,要获得输出激活,我们可以使用`predict`功能。 我们必须使用与先前定义的相同的预处理功能对图像进行预处理,然后再将其提供给 Inception 网络: + +```py +img = preprocess_image(base_image_path) +activations = activation_model.predict(img) +``` + +我们可以绘制这些先前的激活。 一个激活层中的所有过滤器/功能图都可以绘制在网格中。 因此,根据图层中滤镜的数量,我们将图像网格定义为 NumPy 数组,如以下代码所示(以下代码的某些部分是从 [https://blog.keras 借来的。 io / how-convolutional-neural-networks-see-the-world.html](https://blog.keras.io/how-convolutional-neural-networks-see-the-world.html) ): + +```py +import matplotlib.pyplot as plt + +images_per_row = 8 +idx = 1 #activation layer index + +layer_activation=activations[idx] +# This is the number of features in the feature map +n_features = layer_activation.shape[-1] +# The feature map has shape (1, size1, size2, n_features) +r = layer_activation.shape[1] +c = layer_activation.shape[2] + +# We will tile the activation channels in this matrix +n_cols = n_features // images_per_row +display_grid = np.zeros((r * n_cols, images_per_row * c)) +print(display_grid.shape) +``` + +现在,我们将遍历激活层中的所有要素地图,并将缩放后的输出放到网格中,如以下代码所示: + +```py +# We'll tile each filter into this big horizontal grid + for col in range(n_cols): + for row in range(images_per_row): + channel_image = layer_activation[0,:, :, col * + images_per_row + row] + # Post-process the feature to make it visually palatable + channel_image -= channel_image.mean() + channel_image /= channel_image.std() + channel_image *= 64 + channel_image += 128 + channel_image = np.clip(channel_image, 0, + 255).astype('uint8') + display_grid[col * r : (col + 1) * r, + row * c : (row + 1) * c] = channel_image + # Display the grid + scale = 1\. / r + plt.figure(figsize=(scale * display_grid.shape[1], + scale * display_grid.shape[0])) + plt.title(layer_names[idx]+" #filters="+str(n_features)) + plt.grid(False) + plt.imshow(display_grid, aspect='auto', cmap='viridis') +``` + +以下是各层的输出: + +![](img/06ac1e5d-045f-416d-aea9-39cafdbf1e7a.png) + +![](img/66db5895-f2ec-4bb1-94b7-b5dad17f5604.png) + +前面的前两个激活层充当各种边缘检测器的集合。 这些激活保留了初始图片中几乎所有的信息。 + +让我们看下面的屏幕快照,它显示了网络中间的一层。 在这里,它开始识别更高级别的功能,例如鼻子,眼睛,舌头,嘴巴等: + +![](img/f11ab37a-cac5-4045-adb4-19e8ddaf6b2f.png) + +随着我们的上移,地物图在视觉上的解释也越来越少。 较高层的激活会携带有关所看到的特定输入的最少信息,以及有关图像目标类别(在此情况下为狗)的更多信息。 + +可视化 InceptionV3 学习的过滤器的另一种方法是显示每个过滤器输出最大激活值的可视模式。 这可以通过输入空间中的梯度上升来完成。 基本上,通过使用图像空间中的梯度上升进行优化,找到使感兴趣的活动(层中神经元的激活)最大化的输入图像。 最终的输入图像将是所选过滤器最大程度地响应的输入图像。 + +每个激活层都有许多功能图。 以下代码演示了如何从最后一个激活层提取单个功能图。 这个激活值实际上是我们要最大化的损失: + +```py +layer_name = 'activation_94' +filter_index = 0 +layer_output = model.get_layer(layer_name).output +loss = K.mean(layer_output[:, :, :, filter_index]) +``` + +要相对于此`loss`函数计算输入图像的梯度,我们可以如下使用`keras`后端梯度函数: + +```py +grads = K.gradients(loss, model.input)[0] +# We add 1e-5 before dividing so as to avoid accidentally dividing by +# 0. +grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5) +``` + +因此,给定一个激活层和一个可能是随机噪声的起始输入图像,我们可以使用上面的梯度计算应用梯度上升来获得特征图所表示的图案。 跟随`generate_pattern`功能执行相同的操作。 归一化输出模式,以便我们在图像矩阵中具有可行的 RGB 值,这是通过使用`deprocess_image`方法完成的。 以下代码是不言自明的,并具有内联注释来解释每一行: + +```py +def generate_pattern(layer_name, filter_index, size=150): + # Build a loss function that maximizes the activation + # of the nth filter of the layer considered. + layer_output = model.get_layer(layer_name).output + loss = K.mean(layer_output[:, :, :, filter_index]) + # Compute the gradient of the input picture wrt this loss + grads = K.gradients(loss, model.input)[0] + # Normalization trick: we normalize the gradient + grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5) + # This function returns the loss and grads given the input picture + iterate = K.function([model.input], [loss, grads]) + # We start from a gray image with some noise + input_img_data = np.random.random((1, size, size, 3)) * 20 + 128. + # Run gradient ascent for 40 steps + step = 1. + for i in range(40): + loss_value, grads_value = iterate([input_img_data]) + input_img_data += grads_value * step + img = input_img_data[0] + return deprocess_image(img) + +def deprocess_image(x): + # normalize tensor: center on 0., ensure std is 0.1 + x -= x.mean() + x /= (x.std() + 1e-5) + x *= 0.1 + # clip to [0, 1] + x += 0.5 + x = np.clip(x, 0, 1) +``` + +```py + # convert to RGB array + x *= 255 + x = np.clip(x, 0, 255).astype('uint8') + return x +``` + +以下屏幕截图是某些过滤器层的可视化。 第一层具有各种类型的点图案: + +![](img/9b87a770-3ad0-498a-848e-7ab85919b52b.png) + +# 深梦 + +**DeepDream** 是一种艺术性的图像修改技术,它利用了以同名电影命名的深层 CNN 代码 *Inception* 所学习的表示形式。 我们可以拍摄任何输入图像并对其进行处理,以生成令人毛骨悚然的图片,其中充满了算法上的拟南芥伪像,鸟羽毛,狗似的面孔,狗眼-这是 DeepDream 修道院在 ImageNet 上接受过训练的事实,狗在这里繁殖 鸟类种类过多。 + +DeepDream 算法与使用梯度上升的 ConvNet 过滤器可视化技术几乎相同,不同之处在于: + +* 在 DeepDream 中,最大程度地激活了整个图层,而在可视化中,只最大化了一个特定的过滤器,因此将大量特征图的可视化混合在一起 +* 我们不是从随机噪声输入开始,而是从现有图像开始; 因此,最终的可视化效果将修改先前存在的视觉模式,从而以某种艺术性的方式扭曲图像的元素 +* 输入图像以不同的比例(称为**八度**)进行处理,从而提高了可视化效果的质量 + +现在,让我们修改上一部分中的可视化代码。 首先,我们必须更改`loss`功能和梯度计算。 以下是执行相同操作的代码: + +```py +layer_name = 'activation_41' +activation = model.get_layer(layer_name).output + +# We avoid border artifacts by only involving non-border pixels in the #loss. +scaling = K.prod(K.cast(K.shape(activation), 'float32')) +loss = K.sum(K.square(activation[:, 2: -2, 2: -2, :])) / scaling + +# This tensor holds our generated image +dream = model.input + +# Compute the gradients of the dream with regard to the loss. +grads = K.gradients(loss, dream)[0] + +# Normalize gradients. +grads /= K.maximum(K.mean(K.abs(grads)), 1e-7) + +iterate_grad_ac_step = K.function([dream], [loss, grads]) +``` + +第二个变化是输入图像,因此我们必须提供要在其上运行 DeepDream 算法的输入图像。 第三个变化是,我们没有在单个图像上应用渐变强调,而是创建了各种比例的输入图像并应用了渐变强调,如以下代码所示: + +```py +num_octave = 4 # Number of scales at which to run gradient ascent +octave_scale = 1.4 # Size ratio between scales +iterations = 20 # Number of ascent steps per scale + +# If our loss gets larger than 10, +# we will interrupt the gradient ascent process, to avoid ugly +# artifacts +max_loss = 20. + +base_image_path = 'Path to Image You Want to Use' +# Load the image into a Numpy array +img = preprocess_image(base_image_path) +print(img.shape) +# We prepare a list of shape tuples +# defining the different scales at which we will run gradient ascent +original_shape = img.shape[1:3] +successive_shapes = [original_shape] +for i in range(1, num_octave): + shape = tuple([int(dim / (octave_scale ** i)) for dim in + original_shape]) + successive_shapes.append(shape) + +# Reverse list of shapes, so that they are in increasing order +successive_shapes = successive_shapes[::-1] + +# Resize the Numpy array of the image to our smallest scale +original_img = np.copy(img) +shrunk_original_img = resize_img(img, successive_shapes[0]) +print(successive_shapes) + +#Example Octaves for image of shape (1318, 1977) +[(480, 720), (672, 1008), (941, 1412), (1318, 1977)] +``` + +以下代码显示了 DeepDream 算法的一些实用程序功能。 函数`deprocess_image`基本上是 InceptionV3 模型的预处理输入的逆运算符: + +```py +import scipy + +def deprocess_image(x): + # Util function to convert a tensor into a valid image. + if K.image_data_format() == 'channels_first': + x = x.reshape((3, x.shape[2], x.shape[3])) + x = x.transpose((1, 2, 0)) + else: + x = x.reshape((x.shape[1], x.shape[2], 3)) + x /= 2. + x += 0.5 + x *= 255. + x = np.clip(x, 0, 255).astype('uint8') + return x + +def resize_img(img, size): + img = np.copy(img) + factors = (1, + float(size[0]) / img.shape[1], + float(size[1]) / img.shape[2], + 1) + return scipy.ndimage.zoom(img, factors, order=1) + +def save_img(img, fname): + pil_img = deprocess_image(np.copy(img)) + scipy.misc. (fname, pil_img) +``` + +在每个连续的音阶上,从最小到最大的八度音程,我们都执行梯度上升以使该音阶上的先前定义的损耗最大化。 每次渐变爬升后,生成的图像将放大 40%。 在每个升级步骤中,一些图像细节都会丢失; 但是我们可以通过添加丢失的信息来恢复它,因为我们知道该比例的原始图像: + +```py +MAX_ITRN = 20 +MAX_LOSS = 20 +learning_rate = 0.01 + +for shape in successive_shapes: + print('Processing image shape', shape) + img = resize_img(img, shape) + img = gradient_ascent(img, + iterations=MAX_ITRN, + step=learning_rate, + max_loss=MAX_LOSS) + upscaled_shrunk_original_img = resize_img(shrunk_original_img, + shape) + same_size_original = resize_img(original_img, shape) + lost_detail = same_size_original - upscaled_shrunk_original_img + print('adding lost details', lost_detail.shape) + img += lost_detail + shrunk_original_img = resize_img(original_img, shape) + save_img(img, fname='dream_at_scale_' + str(shape) + '.png') + +save_img(img, fname='final_dream.png') +``` + +# 例子 + +以下是 DeepDream 输出的一些示例: + +* 在激活层 41 上运行渐变重音。这是我们之前看到的同一层,带有狗图像输入。 在下面的照片中,您可以看到一些动物从云层和蓝天中冒出来: + +![](img/d521aea6-1fec-43fc-aca4-3b2550d09ca2.png) + +* 在激活层 45 上运行渐变重音。在下图中,您可以看到山上出现了一些类似狗的动物面孔: + +![](img/eff1a0b4-d5ea-4ee0-bc7d-9df87102d461.png) + +* 在激活层 50 上运行渐变。在下面的照片中,您可以看到在蓝天白云下某些特殊的类似叶的图案梦: + +![](img/7e468aa3-1eb1-491a-8955-ee652dc45b57.png) + +生成这些梦想的原始图像在代码存储库中共享。 + +# 摘要 + +在本章中,我们学习了计算机视觉中的算法稀疏。 我们已经解释了如何通过各种可视化技术来解释 CNN 模型,例如基于前向通过的激活可视化,基于梯度上升的过滤器可视化。 最后,我们介绍了 DeepDream 算法,该算法再次是对基于梯度上升的可视化技术的略微修改。 DeepDream 算法是将转移学习应用于计算机视觉或图像处理任务的示例。 + +在下一章中,我们将看到更多类似的应用程序,它们将重点放在样式转换上。 + +# 样式转移 + +绘画需要特殊技能,只有少数人已经掌握。 绘画呈现出内容和风格的复杂相互作用。 另一方面,照片是视角和光线的结合。 当两者结合时,结果是惊人的和令人惊讶的。 该过程称为**艺术风格转移**。 以下是一个示例,其中输入图像是德国图宾根的 Neckarfront,风格图像是 Vincent van Gogh 着名的画作 *The Starry Night* 。 有趣,不是吗? 看一下以下图像: + +![](img/794949a4-3e20-4ad0-8d38-060bc707b82a.png) + +左图:描绘德国蒂宾根 Neckarfront 的原始照片。 右图:为相应生成的图像提供样式的绘画(插图:Vincent van Gogh 的《星夜》)。 来源:*一种艺术风格的神经算法*,Gatys 等人。 (arXiv:1508.06576v2) + +如果您仔细查看前面的图像,则右侧的绘画风格图像似乎已经从左侧的照片中拾取了内容。 绘画的样式,颜色和笔触样式产生了最终结果。 令人着迷的结果是 Gatys 等人在论文*一种用于艺术风格的神经算法*中提出的一种转移学习算法的结果。 ( [https://arxiv.org/abs/1508.06576](https://arxiv.org/abs/1508.06576) )。 我们将从实现的角度讨论本文的复杂性,并了解如何自己执行此技术。 + +在本章中,我们将专注于利用深度学习和传递学习来构建神经样式传递系统。 本章重点关注的领域包括: + +* 了解神经样式转换 +* 图像预处理方法 +* 建筑损失功能 +* 构造自定义优化器 +* 样式转移实战 + +我们将涵盖有关神经样式转移,损失函数和优化的理论概念。 除此之外,我们将使用动手方法来实现我们自己的神经样式转换模型。 本章的代码可在 [https://github.com/dipanjanS/hands-on-transfer-learning-with-python](https://github.com/dipanjanS/hands-on-transfer-learning-with-python) 的 GitHub 存储库的第 10 章文件夹中快速参考。 请根据需要参考本章。 + +# 了解神经样式转换 + +**神经样式转移**是将参考图像的**样式**应用于特定目标图像的过程,以使目标图像的原始**内容**保持不变。 在这里,样式定义为参考图像中存在的颜色,图案和纹理,而内容定义为图像的整体结构和更高层次的组件。 + +在此,主要目的是保留原始目标图像的内容,同时在目标图像上叠加或采用参考图像的样式。 为了从数学上定义这个概念,请考虑三个图像:原始内容(表示为 **c** ),参考样式(表示为 **s** )和生成的图像(表示为 **g** )。 我们需要一种方法来衡量在内容方面, *c* 和 *g* 不同的图像的程度。 同样,就输出的样式特征而言,与样式图像相比,输出图像应具有较小的差异。 形式上,神经样式转换的目标函数可以表述为: + +![](img/f6f9d3f8-d00f-414c-bd48-83905e444b82.png) + +此处,*α*和*β*是用于控制内容和样式成分对整体损失的影响的权重。 此描述可以进一步简化,并表示如下: + +![](img/1f77aa7c-844e-4579-b42b-f5febbdfa09b.png) + +在这里,我们可以根据前面的公式定义以下组件: + +* `dist`是规范函数; 例如,L 2 规范距离 +* `style(...)`是用于为参考样式和生成的图像计算样式表示的函数 +* `content(...)`是一种功能,可为原始内容和生成的图像计算内容的表示形式 +* *I c* , *I s* 和 *I g* ,并分别生成图像 + +因此,最小化此损失会导致*样式(I g )*接近*样式(I s )*,以及 *含量(I g )*接近*含量(I c )*。 这有助于我们达成有效的样式转换所需的规定。 我们将尝试最小化的损失函数包括三个部分: 即将讨论的**内容损失**,**样式损失**和**总变化损失**。 关键思想或目标是保留原始目标图像的*内容*,同时在目标图像上叠加或采用参考图像的*样式*。 此外,在神经样式转换的背景下,您应该记住以下几点: + +* **样式**可以定义为参考图像中存在的调色板,特定图案和纹理 +* **内容**可以定义为原始目标图像的整体结构和更高级别的组件 + +到目前为止,我们知道深度学习对于计算机视觉的真正威力在于利用诸如深层**卷积神经网络**( **CNN** )模型之类的模型,这些模型可用于提取正确的图像 构建这些损失函数时的表示形式。 在本章中,我们将使用转移学习的原理来构建用于神经样式转移的系统,以提取最佳特征。 在前面的章节中,我们已经讨论了与计算机视觉相关的任务的预训练模型。 在本章中,我们将再次使用流行的 VGG-16 模型作为特征提取器。 执行神经样式转换的主要步骤如下所示: + +* 利用 VGG-16 帮助计算样式,内容和生成图像的图层激活 +* 使用这些激活来定义前面提到的特定损失函数 +* 最后,使用梯度下降来最大程度地减少总损耗 + +如果您想更深入地研究神经样式转换背后的核心原理和理论概念,建议您阅读以下文章: + +* *一种艺术风格的神经算法*,作者:Leon A. Gatys,Alexander S. Ecker 和 Matthias Bethge( [https://arxiv.org/abs/1508.06576](https://arxiv.org/abs/1508.06576) ) +* 贾斯汀·约翰逊(Justin Johnson),亚历山大·阿拉西(Alexandre Alahi)和李菲菲( [https://arxiv.org/abs/1603.08155](https://arxiv.org/abs/1603.08155) )带来的*实时样式转换和超分辨率的感知损失* + +# 图像预处理方法 + +在这种情况下,实现此类网络的第一步也是最重要的一步是对数据或图像进行预处理。 以下代码段显示了一些用于对图像进行大小和通道调整的快速实用程序: + +```py +import numpy as np +from keras.applications import vgg16 +from keras.preprocessing.image import load_img, img_to_array + +def preprocess_image(image_path, height=None, width=None): + height = 400 if not height else height + width = width if width else int(width * height / height) + img = load_img(image_path, target_size=(height, width)) + img = img_to_array(img) + img = np.expand_dims(img, axis=0) + img = vgg16.preprocess_input(img) + return img + +def deprocess_image(x): + # Remove zero-center by mean pixel + x[:, :, 0] += 103.939 + x[:, :, 1] += 116.779 + x[:, :, 2] += 123.68 + # 'BGR'->'RGB' + x = x[:, :, ::-1] + x = np.clip(x, 0, 255).astype('uint8') + return x +``` + +当我们要编写自定义损失函数和操作例程时,我们将需要定义某些占位符。 请记住,`keras`是一个利用张量操作后端(例如`tensorflow`,`theano`和`CNTK`)执行繁重工作的高级库。 因此,这些占位符提供了高级抽象来与基础张量对象一起使用。 以下代码段为样式,内容和生成的图像以及神经网络的输入张量准备了占位符: + +```py +from keras import backend as K + +# This is the path to the image you want to transform. +TARGET_IMG = 'lotr.jpg' +# This is the path to the style image. +REFERENCE_STYLE_IMG = 'pattern1.jpg' + +width, height = load_img(TARGET_IMG).size +img_height = 480 +img_width = int(width * img_height / height) + +target_image = K.constant(preprocess_image(TARGET_IMG, + height=img_height, + width=img_width)) +style_image = K.constant(preprocess_image(REFERENCE_STYLE_IMG, + height=img_height, + width=img_width)) + +# Placeholder for our generated image +generated_image = K.placeholder((1, img_height, img_width, 3)) + +# Combine the 3 images into a single batch +input_tensor = K.concatenate([target_image, + style_image, + generated_image], axis=0) +``` + +我们将像前几章一样加载预训练的 VGG-16 模型。 也就是说,没有顶部的全连接层。 唯一的区别是我们将为模型输入提供输入张量的大小尺寸。 以下代码段有助于我们构建预训练模型: + +```py +model = vgg16.VGG16(input_tensor=input_tensor, + weights='imagenet', + include_top=False) +``` + +# 建筑损失功能 + +如背景小节所述,神经样式转移的问题围绕内容和样式的损失函数。 在本小节中,我们将讨论和定义所需的损失函数。 + +# 内容丢失 + +在任何基于 CNN 的模型中,来自顶层的激活都包含更多的全局和抽象信息(例如,诸如人脸之类的高级结构),而底层将包含局部信息(例如,诸如眼睛,鼻子, 边缘和角落)。 我们希望利用 CNN 的顶层来捕获图像内容的正确表示。 因此,对于内容损失,考虑到我们将使用预训练的 VGG-16 模型,我们可以将损失函数定义为通过计算得出的顶层激活(给出特征表示)之间的 L2 范数(缩放和平方的欧几里得距离)。 目标图像,以及在生成的图像上计算的同一层的激活。 假设我们通常从 CNN 的顶层获得与图像内容相关的特征表示,则预期生成的图像看起来与基本目标图像相似。 以下代码段显示了计算内容丢失的函数: + +```py +def content_loss(base, combination): + return K.sum(K.square(combination - base)) +``` + +# 风格损失 + +关于神经风格转移的原始论文*,一种由神经科学风格的神经算法*,由 Gatys 等人撰写。 ( [https://arxiv.org/abs/1508.06576](https://arxiv.org/abs/1508.06576) ),利用 CNN 中的多个卷积层(而不是一个)来从参考样式图像中提取有意义的样式和表示,以捕获与外观或样式有关的信息 不论图像内容如何,​​在所有空间尺度上都可以。 样式表示可计算 CNN 不同层中不同要素之间的相关性。 + +忠于原始论文,我们将利用 **Gram 矩阵**并在由卷积层生成的特征表示上进行计算。 Gram 矩阵计算在任何给定的 conv 层中生成的特征图之间的内积。 内积项与相应特征集的协方差成正比,因此可以捕获趋于一起激活的图层的特征之间的相关性。 这些特征相关性有助于捕获特定空间比例的图案的相关汇总统计信息,这些统计信息与样式,纹理和外观相对应,而不与图像中存在的组件和对象相对应。 + +因此,样式损失定义为参考样式的 Gram 矩阵与生成的图像之间的差异的按比例缩放的 Frobenius 范数(矩阵上的欧几里得范数)。 最小化此损失有助于确保参考样式图像中不同空间比例下找到的纹理在生成的图像中相似。 因此,以下代码段基于 Gram 矩阵计算定义了样式损失函数: + +```py +def style_loss(style, combination, height, width): + + def build_gram_matrix(x): + features = K.batch_flatten(K.permute_dimensions(x, (2, 0, 1))) + gram_matrix = K.dot(features, K.transpose(features)) + return gram_matrix + + S = build_gram_matrix(style) + C = build_gram_matrix(combination) + channels = 3 + size = height * width + return K.sum(K.square(S - C))/(4\. * (channels ** 2) * (size ** 2)) +``` + +# 总变化损失 + +据观察,仅减少样式和内容损失的优化会导致高度像素化和嘈杂的输出。 为了解决这个问题,引入了总变化损失。 **总变化损失**与*正则化*损失相似。 引入此方法是为了确保生成的图像中的空间连续性和平滑性,以避免产生嘈杂的像素化结果。 在函数中的定义如下: + +```py +def total_variation_loss(x): + a = K.square( + x[:, :img_height - 1, :img_width - 1, :] - x[:, 1:, :img_width + - 1, :]) + b = K.square( + x[:, :img_height - 1, :img_width - 1, :] - x[:, :img_height - + 1, 1:, :]) + return K.sum(K.pow(a + b, 1.25)) +``` + +# 总损失函数 + +在定义了用于神经样式传递的整体损失函数的组成部分之后,下一步就是将这些构造块缝合在一起。 由于内容和样式信息是由 CNN 在网络中的不同深度捕获的,因此我们需要针对每种损失类型在适当的层上应用和计算损失。 我们将对 conv 图层进行 1 到 5 层的样式损失,并为每一层设置适当的权重。 + +这是构建整体损失函数的代码片段: + +```py +# weights for the weighted average loss function +content_weight = 0.05 +total_variation_weight = 1e-4 + +content_layer = 'block4_conv2' +style_layers = ['block1_conv2', 'block2_conv2', + 'block3_conv3','block4_conv3', 'block5_conv3'] +style_weights = [0.1, 0.15, 0.2, 0.25, 0.3] + +# initialize total loss +loss = K.variable(0.) + +# add content loss +layer_features = layers[content_layer] +target_image_features = layer_features[0, :, :, :] +combination_features = layer_features[2, :, :, :] +loss += content_weight * content_loss(target_image_features, + combination_features) + +# add style loss +for layer_name, sw in zip(style_layers, style_weights): + layer_features = layers[layer_name] + style_reference_features = layer_features[1, :, :, :] + combination_features = layer_features[2, :, :, :] + sl = style_loss(style_reference_features, combination_features, + height=img_height, width=img_width) + loss += (sl*sw) + +# add total variation loss +loss += total_variation_weight * total_variation_loss(generated_image) +``` + +# 构造自定义优化器 + +目的是在优化算法的帮助下迭代地使总损失最小化。 Gatys 等人的论文中,使用 L-BFGS 算法进行了优化,该算法是基于准牛顿法的一种优化算法,通常用于解决非线性优化问题和参数估计。 该方法通常比标准梯度下降收敛更快。 + +SciPy 在`scipy.optimize.fmin_l_bfgs_b()`中提供了一个实现。 但是,局限性包括该函数仅适用于平面一维矢量,这与我们正在处理的三维图像矩阵不同,并且损失函数和梯度的值需要作为两个单独的函数传递。 我们基于模式构建一个`Evaluator`类,然后由`keras`创建者 FrançoisChollet 创建,以一次计算损失和梯度值,而不是独立和单独的计算。 这将在首次调用时返回损耗值,并将缓存下一次调用的梯度。 因此,这将比独立计算两者更为有效。 以下代码段定义了`Evaluator`类: + +```py +class Evaluator(object): + + def __init__(self, height=None, width=None): + self.loss_value = None + self.grads_values = None + self.height = height + self.width = width + + def loss(self, x): + assert self.loss_value is None + x = x.reshape((1, self.height, self.width, 3)) + outs = fetch_loss_and_grads([x]) + loss_value = outs[0] + grad_values = outs[1].flatten().astype('float64') + self.loss_value = loss_value + self.grad_values = grad_values + return self.loss_value + + def grads(self, x): + assert self.loss_value is not None + grad_values = np.copy(self.grad_values) + self.loss_value = None + self.grad_values = None + return grad_values + +evaluator = Evaluator(height=img_height, width=img_width) +``` + +# 样式转移实战 + +难题的最后一步是使用所有构建块并在操作中执行样式转换! 可以从数据目录中获取艺术/样式和内容图像,以供参考。 以下代码片段概述了如何评估损耗和梯度。 我们还按规律的间隔/迭代(`5`,`10`等)写回输出,以了解神经样式转移的过程如何在经过一定的迭代次数后考虑的图像转换图像,如以下代码段所示: + +```py +from scipy.optimize import fmin_l_bfgs_b +from scipy.misc import imsave +from imageio import imwrite +import time + +result_prefix = 'st_res_'+TARGET_IMG.split('.')[0] +iterations = 20 + +# Run scipy-based optimization (L-BFGS) over the pixels of the +# generated image +# so as to minimize the neural style loss. +# This is our initial state: the target image. +# Note that `scipy.optimize.fmin_l_bfgs_b` can only process flat +# vectors. +x = preprocess_image(TARGET_IMG, height=img_height, width=img_width) +x = x.flatten() + +for i in range(iterations): + print('Start of iteration', (i+1)) + start_time = time.time() + x, min_val, info = fmin_l_bfgs_b(evaluator.loss, x, + fprime=evaluator.grads, maxfun=20) + print('Current loss value:', min_val) + if (i+1) % 5 == 0 or i == 0: + # Save current generated image only every 5 iterations + img = x.copy().reshape((img_height, img_width, 3)) + img = deprocess_image(img) + fname = result_prefix + '_iter%d.png' %(i+1) + imwrite(fname, img) + print('Image saved as', fname) + end_time = time.time() + print('Iteration %d completed in %ds' % (i+1, end_time - start_time)) +``` + +到现在为止,必须非常明显的是,神经样式转换是一项计算量巨大的任务。 对于所考虑的图像集,在具有 8GB RAM 的 Intel i5 CPU 上,每次迭代花费了 500-1,000 秒(尽管在 i7 或 Xeon 处理器上要快得多!)。 以下代码段显示了我们在 AWS 的 p2.x 实例上使用 GPU 所获得的加速,每次迭代仅需 25 秒! 以下代码片段还显示了一些迭代的输出。 我们打印每次迭代的损失和时间,并在每五次迭代后保存生成的图像: + +```py +Start of iteration 1 +Current loss value: 10028529000.0 +Image saved as st_res_lotr_iter1.png +Iteration 1 completed in 28s +Start of iteration 2 +Current loss value: 5671338500.0 +Iteration 2 completed in 24s +Start of iteration 3 +Current loss value: 4681865700.0 +Iteration 3 completed in 25s +Start of iteration 4 +Current loss value: 4249350400.0 +. +. +. +Start of iteration 20 +Current loss value: 3458219000.0 +Image saved as st_res_lotr_iter20.png +Iteration 20 completed in 25s +``` + +现在,您将学习神经样式转移模型如何考虑内容图像的样式转移。 请记住,我们在某些迭代之后为每对样式和内容图像执行了检查点输出。 我们利用`matplotlib`和`skimage`加载并了解我们系统执行的样式转换魔术! + +我们将非常受欢迎的*指环王*电影中的以下图像用作我们的内容图像,并将基于花卉图案的精美艺术品用作我们的样式图像: + +![](img/27afcdb0-5ba3-4a05-8278-8ed5112261a8.png) + +在以下代码段中,我们将在各种迭代之后加载生成的样式化图像: + +```py +from skimage import io +from glob import glob +from matplotlib import pyplot as plt + +%matplotlib inline +content_image = io.imread('lotr.jpg') +style_image = io.imread('pattern1.jpg') + +iter1 = io.imread('st_res_lotr_iter1.png') +iter5 = io.imread('st_res_lotr_iter5.png') +iter10 = io.imread('st_res_lotr_iter10.png') +iter15 = io.imread('st_res_lotr_iter15.png') +iter20 = io.imread('st_res_lotr_iter20.png') +fig = plt.figure(figsize = (15, 15)) +ax1 = fig.add_subplot(6,3, 1) +ax1.imshow(content_image) +t1 = ax1.set_title('Original') + +gen_images = [iter1,iter5, iter10, iter15, iter20] + +for i, img in enumerate(gen_images): + ax1 = fig.add_subplot(6,3,i+1) + ax1.imshow(content_image) + t1 = ax1.set_title('Iteration {}'.format(i+5)) +plt.tight_layout() +fig.subplots_adjust(top=0.95) +t = fig.suptitle('LOTR Scene after Style Transfer') +``` + +以下是显示原始图像和每五次迭代后生成的样式图像的输出: + +![](img/7bb09bf1-7394-43ba-b4a0-a17eee6abe1c.png) + +以下是高分辨率的最终样式图像。 您可以清楚地看到花卉图案的纹理和样式是如何在原始*指环王*电影图像中慢慢传播的,并赋予了其良好的复古外观: + +![](img/58ca16d2-f5bc-48dc-8e18-44aa17afaa25.png) + +让我们再举一个样式转移示例。 下图包含我们的内容图像,即来自黑豹的著名虚构的城市瓦卡达。 风格图片是梵高非常受欢迎的画作 *The Starry Night,*! 我们将在样式传递系统中将它们用作输入图像: + +![](img/006dc7f4-1cc1-458d-a29d-7229ed1b7c9d.png) + +以下是高分辨率的最终样式图像,显示在下面的图像中。 您可以清楚地看到样式绘画中的纹理,边缘,颜色和图案如何传播到城市内容图像中: + +![](img/6be810e1-6824-4697-a3f4-46705e71d681.png) + +天空和建筑物采用了与您在绘画中可以观察到的非常相似的形式,但是内容图像的整体结构得以保留。 令人着迷,不是吗? 现在用您自己感兴趣的图像尝试一下! + +# 摘要 + +本章介绍了深度学习领域中一种非常新颖的技术,它利用了深度学习的力量来创造艺术! 确实,数据科学既是一门艺术,也是正确使用数据的科学,而创新则是推动这一发展的事物。 我们介绍了神经样式转移的核心概念,如何使用有效的损失函数来表示和表达问题,以及如何利用转移学习的力量和像 VGG-16 这样的预训练模型来提取正确的特征表示。 + +计算机视觉领域不断发展,深度学习与迁移学习相结合为创新和构建新颖的应用打开了大门。 本章中的示例应帮助您了解该领域的广泛新颖性,并使您能够走出去并尝试新技术,模型和方法来构建诸如神经样式转换的系统! 随之而来的是有关图像标题和着色的更有趣,更复杂的案例研究。 敬请关注! \ No newline at end of file diff --git a/docs/handson-tl-py/img/006dc7f4-1cc1-458d-a29d-7229ed1b7c9d.png b/docs/handson-tl-py/img/006dc7f4-1cc1-458d-a29d-7229ed1b7c9d.png new file mode 100644 index 0000000000000000000000000000000000000000..fbd7644958ebc2d5c96e965b458c41e03305a5fe Binary files /dev/null and b/docs/handson-tl-py/img/006dc7f4-1cc1-458d-a29d-7229ed1b7c9d.png differ diff --git a/docs/handson-tl-py/img/00a99297-3d3b-4387-a888-ff58f137672d.png b/docs/handson-tl-py/img/00a99297-3d3b-4387-a888-ff58f137672d.png new file mode 100644 index 0000000000000000000000000000000000000000..5dee3ea45b184bc58823edeee73793bd9ad15bfc Binary files /dev/null and b/docs/handson-tl-py/img/00a99297-3d3b-4387-a888-ff58f137672d.png differ diff --git a/docs/handson-tl-py/img/0284727e-ebf8-4bc9-8074-24022d4a2558.png b/docs/handson-tl-py/img/0284727e-ebf8-4bc9-8074-24022d4a2558.png new file mode 100644 index 0000000000000000000000000000000000000000..3358a1d49941c9cc0f3e4490f5899da499c70584 Binary files /dev/null and b/docs/handson-tl-py/img/0284727e-ebf8-4bc9-8074-24022d4a2558.png differ diff --git a/docs/handson-tl-py/img/02c4af7a-3e62-4c89-8779-4986116508b2.png b/docs/handson-tl-py/img/02c4af7a-3e62-4c89-8779-4986116508b2.png new file mode 100644 index 0000000000000000000000000000000000000000..7c80fcb180380b8a3c9cf6a48e5e7dfa4e7ce755 Binary files /dev/null and b/docs/handson-tl-py/img/02c4af7a-3e62-4c89-8779-4986116508b2.png differ diff --git a/docs/handson-tl-py/img/03d09bc9-6d58-44b0-ae56-9fca79a2c304.png b/docs/handson-tl-py/img/03d09bc9-6d58-44b0-ae56-9fca79a2c304.png new file mode 100644 index 0000000000000000000000000000000000000000..2a764353e52078797fae57dc8e8f35652057076e Binary files /dev/null and b/docs/handson-tl-py/img/03d09bc9-6d58-44b0-ae56-9fca79a2c304.png differ diff --git a/docs/handson-tl-py/img/03d7d7d4-46f1-4cf2-a857-d315b9503cef.png b/docs/handson-tl-py/img/03d7d7d4-46f1-4cf2-a857-d315b9503cef.png new file mode 100644 index 0000000000000000000000000000000000000000..0b8bc26eb76219f1235ea600a005a59837a5ddf3 Binary files /dev/null and b/docs/handson-tl-py/img/03d7d7d4-46f1-4cf2-a857-d315b9503cef.png differ diff --git a/docs/handson-tl-py/img/044603aa-f36f-4c54-b019-71057b3fb0b1.png b/docs/handson-tl-py/img/044603aa-f36f-4c54-b019-71057b3fb0b1.png new file mode 100644 index 0000000000000000000000000000000000000000..4c51c1b9d728d900eb0f97bfb098444dc49e7b8a Binary files /dev/null and b/docs/handson-tl-py/img/044603aa-f36f-4c54-b019-71057b3fb0b1.png differ diff --git a/docs/handson-tl-py/img/04c9762f-de07-4d2a-885b-beee4afb50ae.png b/docs/handson-tl-py/img/04c9762f-de07-4d2a-885b-beee4afb50ae.png new file mode 100644 index 0000000000000000000000000000000000000000..c30d6f7794a1f7a99f41f79acb18c104f92097f3 Binary files /dev/null and b/docs/handson-tl-py/img/04c9762f-de07-4d2a-885b-beee4afb50ae.png differ diff --git a/docs/handson-tl-py/img/04d75f26-123b-4862-ab2a-fcb28b932d36.png b/docs/handson-tl-py/img/04d75f26-123b-4862-ab2a-fcb28b932d36.png new file mode 100644 index 0000000000000000000000000000000000000000..957504a1bd0e07e994a60ebe014e2b2acc7f61fd Binary files /dev/null and b/docs/handson-tl-py/img/04d75f26-123b-4862-ab2a-fcb28b932d36.png differ diff --git a/docs/handson-tl-py/img/04ef4106-bd1b-484b-a744-92388cbf2bcd.png b/docs/handson-tl-py/img/04ef4106-bd1b-484b-a744-92388cbf2bcd.png new file mode 100644 index 0000000000000000000000000000000000000000..e017a4219f08750be14eb46d0fb51802d8a794bc Binary files /dev/null and b/docs/handson-tl-py/img/04ef4106-bd1b-484b-a744-92388cbf2bcd.png differ diff --git a/docs/handson-tl-py/img/05544f28-196c-4245-83df-4fcf348f236d.png b/docs/handson-tl-py/img/05544f28-196c-4245-83df-4fcf348f236d.png new file mode 100644 index 0000000000000000000000000000000000000000..d74a4a21a362e7f019ab5374cea815dff02dc5bd Binary files /dev/null and b/docs/handson-tl-py/img/05544f28-196c-4245-83df-4fcf348f236d.png differ diff --git a/docs/handson-tl-py/img/05f06392-f5cc-44ff-8755-ebeded169ff2.png b/docs/handson-tl-py/img/05f06392-f5cc-44ff-8755-ebeded169ff2.png new file mode 100644 index 0000000000000000000000000000000000000000..77cb3c871b7d03e89497f5faa4672531f4fd13bc Binary files /dev/null and b/docs/handson-tl-py/img/05f06392-f5cc-44ff-8755-ebeded169ff2.png differ diff --git a/docs/handson-tl-py/img/06ac1e5d-045f-416d-aea9-39cafdbf1e7a.png b/docs/handson-tl-py/img/06ac1e5d-045f-416d-aea9-39cafdbf1e7a.png new file mode 100644 index 0000000000000000000000000000000000000000..4ec9b8d6d17d397fb47189599f931a4feba4161f Binary files /dev/null and b/docs/handson-tl-py/img/06ac1e5d-045f-416d-aea9-39cafdbf1e7a.png differ diff --git a/docs/handson-tl-py/img/0717c719-04d7-4cda-9c8b-08b781d8dfa8.png b/docs/handson-tl-py/img/0717c719-04d7-4cda-9c8b-08b781d8dfa8.png new file mode 100644 index 0000000000000000000000000000000000000000..4ad40fd522d1936ec15c1067f315d3424bd7a366 Binary files /dev/null and b/docs/handson-tl-py/img/0717c719-04d7-4cda-9c8b-08b781d8dfa8.png differ diff --git a/docs/handson-tl-py/img/08311175-d8ce-438e-85e9-2bdb6e68cb29.png b/docs/handson-tl-py/img/08311175-d8ce-438e-85e9-2bdb6e68cb29.png new file mode 100644 index 0000000000000000000000000000000000000000..268330d00e4c6518e24a6e38f43fc5001d9351e0 Binary files /dev/null and b/docs/handson-tl-py/img/08311175-d8ce-438e-85e9-2bdb6e68cb29.png differ diff --git a/docs/handson-tl-py/img/08624886-c3d1-4202-a1a6-9121e0ab4c19.jpg b/docs/handson-tl-py/img/08624886-c3d1-4202-a1a6-9121e0ab4c19.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8d526ec32c9f4c4f5a801f9c86b1cae8c88e2500 Binary files /dev/null and b/docs/handson-tl-py/img/08624886-c3d1-4202-a1a6-9121e0ab4c19.jpg differ diff --git a/docs/handson-tl-py/img/0864c303-7dad-4af7-a015-f9cb1c2dbaba.png b/docs/handson-tl-py/img/0864c303-7dad-4af7-a015-f9cb1c2dbaba.png new file mode 100644 index 0000000000000000000000000000000000000000..98787f6f84f81f9cc3303e06ba9375fcc5bd4d5a Binary files /dev/null and b/docs/handson-tl-py/img/0864c303-7dad-4af7-a015-f9cb1c2dbaba.png differ diff --git a/docs/handson-tl-py/img/0890948e-245c-4806-90f2-b79e2218dbbe.png b/docs/handson-tl-py/img/0890948e-245c-4806-90f2-b79e2218dbbe.png new file mode 100644 index 0000000000000000000000000000000000000000..ddaa5c18db9feba776eda745eb8238b7ace1b2c9 Binary files /dev/null and b/docs/handson-tl-py/img/0890948e-245c-4806-90f2-b79e2218dbbe.png differ diff --git a/docs/handson-tl-py/img/08a67c09-f33c-4ff9-aa7c-0d8d43e74d6d.png b/docs/handson-tl-py/img/08a67c09-f33c-4ff9-aa7c-0d8d43e74d6d.png new file mode 100644 index 0000000000000000000000000000000000000000..1a546259556f35127e5a98461bbb344abae7a197 Binary files /dev/null and b/docs/handson-tl-py/img/08a67c09-f33c-4ff9-aa7c-0d8d43e74d6d.png differ diff --git a/docs/handson-tl-py/img/0952f060-fd33-4b79-a530-a3ef4c96a006.png b/docs/handson-tl-py/img/0952f060-fd33-4b79-a530-a3ef4c96a006.png new file mode 100644 index 0000000000000000000000000000000000000000..6228b092a050902d6f01b1d1c00fec7b87aa8881 Binary files /dev/null and b/docs/handson-tl-py/img/0952f060-fd33-4b79-a530-a3ef4c96a006.png differ diff --git a/docs/handson-tl-py/img/099f52bb-312d-4d9b-a510-8824481123e2.png b/docs/handson-tl-py/img/099f52bb-312d-4d9b-a510-8824481123e2.png new file mode 100644 index 0000000000000000000000000000000000000000..e2999dc3195852c71e9dd5c61cd4228083f5b715 Binary files /dev/null and b/docs/handson-tl-py/img/099f52bb-312d-4d9b-a510-8824481123e2.png differ diff --git a/docs/handson-tl-py/img/0b27b8e0-ff6f-4d4f-9730-ef7c4378446c.png b/docs/handson-tl-py/img/0b27b8e0-ff6f-4d4f-9730-ef7c4378446c.png new file mode 100644 index 0000000000000000000000000000000000000000..736a9055161c927ef0ed4bdd09559bb2debafc51 Binary files /dev/null and b/docs/handson-tl-py/img/0b27b8e0-ff6f-4d4f-9730-ef7c4378446c.png differ diff --git a/docs/handson-tl-py/img/0b8a131c-6589-4e31-bf10-c6de946f210a.png b/docs/handson-tl-py/img/0b8a131c-6589-4e31-bf10-c6de946f210a.png new file mode 100644 index 0000000000000000000000000000000000000000..6fbaf0ca7145565de4e99eb75c0c7a30e1196e88 Binary files /dev/null and b/docs/handson-tl-py/img/0b8a131c-6589-4e31-bf10-c6de946f210a.png differ diff --git a/docs/handson-tl-py/img/0c77c2ea-ffb1-4db5-8e3b-35514628b0d0.png b/docs/handson-tl-py/img/0c77c2ea-ffb1-4db5-8e3b-35514628b0d0.png new file mode 100644 index 0000000000000000000000000000000000000000..d1c023e2b8b51df1bfa207addc8b7ff0e3df75bf Binary files /dev/null and b/docs/handson-tl-py/img/0c77c2ea-ffb1-4db5-8e3b-35514628b0d0.png differ diff --git a/docs/handson-tl-py/img/0c813b73-4ddd-42c6-a8c1-79fc1597e8f2.png b/docs/handson-tl-py/img/0c813b73-4ddd-42c6-a8c1-79fc1597e8f2.png new file mode 100644 index 0000000000000000000000000000000000000000..68cd2f99cf857eeb1b0cbb42ca817f1b6e9eef5f Binary files /dev/null and b/docs/handson-tl-py/img/0c813b73-4ddd-42c6-a8c1-79fc1597e8f2.png differ diff --git a/docs/handson-tl-py/img/0cc54c5b-30ef-4192-9989-6fc550bee90b.png b/docs/handson-tl-py/img/0cc54c5b-30ef-4192-9989-6fc550bee90b.png new file mode 100644 index 0000000000000000000000000000000000000000..6ef7eb7318d40774e08680f623995c37effaebe3 Binary files /dev/null and b/docs/handson-tl-py/img/0cc54c5b-30ef-4192-9989-6fc550bee90b.png differ diff --git a/docs/handson-tl-py/img/0cf59473-54cf-44b5-a5d3-36699ee1e65c.png b/docs/handson-tl-py/img/0cf59473-54cf-44b5-a5d3-36699ee1e65c.png new file mode 100644 index 0000000000000000000000000000000000000000..b0498354b5fd55cdebebb93e7511c82e293eaa3c Binary files /dev/null and b/docs/handson-tl-py/img/0cf59473-54cf-44b5-a5d3-36699ee1e65c.png differ diff --git a/docs/handson-tl-py/img/0cff1c62-e1c0-40ec-b7a8-cad1f3da34b0.png b/docs/handson-tl-py/img/0cff1c62-e1c0-40ec-b7a8-cad1f3da34b0.png new file mode 100644 index 0000000000000000000000000000000000000000..9a7519734496f86a2d748f93e3bd7988aa3ed4f7 Binary files /dev/null and b/docs/handson-tl-py/img/0cff1c62-e1c0-40ec-b7a8-cad1f3da34b0.png differ diff --git a/docs/handson-tl-py/img/0d2a1479-384c-4e61-b85e-ff85ae34a29f.png b/docs/handson-tl-py/img/0d2a1479-384c-4e61-b85e-ff85ae34a29f.png new file mode 100644 index 0000000000000000000000000000000000000000..7c80fcb180380b8a3c9cf6a48e5e7dfa4e7ce755 Binary files /dev/null and b/docs/handson-tl-py/img/0d2a1479-384c-4e61-b85e-ff85ae34a29f.png differ diff --git a/docs/handson-tl-py/img/0ee04466-01a3-4669-85c2-723a98d3aec6.png b/docs/handson-tl-py/img/0ee04466-01a3-4669-85c2-723a98d3aec6.png new file mode 100644 index 0000000000000000000000000000000000000000..12eb797e8588286e3ddc499c0d07cb285763386d Binary files /dev/null and b/docs/handson-tl-py/img/0ee04466-01a3-4669-85c2-723a98d3aec6.png differ diff --git a/docs/handson-tl-py/img/113b62ab-656d-4d22-9e1c-26c2855cf9e5.png b/docs/handson-tl-py/img/113b62ab-656d-4d22-9e1c-26c2855cf9e5.png new file mode 100644 index 0000000000000000000000000000000000000000..79bf726012fbe2c9e70a98dbb7123b73bc23a66d Binary files /dev/null and b/docs/handson-tl-py/img/113b62ab-656d-4d22-9e1c-26c2855cf9e5.png differ diff --git a/docs/handson-tl-py/img/11b43902-51d6-40ce-914e-052f4d4571e9.png b/docs/handson-tl-py/img/11b43902-51d6-40ce-914e-052f4d4571e9.png new file mode 100644 index 0000000000000000000000000000000000000000..1043ff7cf8c730fabfbad1ed9ad2e01740d0b29c Binary files /dev/null and b/docs/handson-tl-py/img/11b43902-51d6-40ce-914e-052f4d4571e9.png differ diff --git a/docs/handson-tl-py/img/11b7469a-fafe-40c9-a0c8-ea3d9f083f33.png b/docs/handson-tl-py/img/11b7469a-fafe-40c9-a0c8-ea3d9f083f33.png new file mode 100644 index 0000000000000000000000000000000000000000..2f33fbfa4d2d2fe9139c88088fc4e2710f139006 Binary files /dev/null and b/docs/handson-tl-py/img/11b7469a-fafe-40c9-a0c8-ea3d9f083f33.png differ diff --git a/docs/handson-tl-py/img/11fdb71c-0ba2-48d1-a1ba-a28381d1a907.png b/docs/handson-tl-py/img/11fdb71c-0ba2-48d1-a1ba-a28381d1a907.png new file mode 100644 index 0000000000000000000000000000000000000000..6168b6fbaa3d0553ed5114e7d64c357df19794ec Binary files /dev/null and b/docs/handson-tl-py/img/11fdb71c-0ba2-48d1-a1ba-a28381d1a907.png differ diff --git a/docs/handson-tl-py/img/12c9ca72-544a-460a-8d0c-023b1eda7747.png b/docs/handson-tl-py/img/12c9ca72-544a-460a-8d0c-023b1eda7747.png new file mode 100644 index 0000000000000000000000000000000000000000..ca0268cc76bb2019917d5868e2130d5612209cb0 Binary files /dev/null and b/docs/handson-tl-py/img/12c9ca72-544a-460a-8d0c-023b1eda7747.png differ diff --git a/docs/handson-tl-py/img/12eeeba8-9883-4473-a839-66a9d7304c32.png b/docs/handson-tl-py/img/12eeeba8-9883-4473-a839-66a9d7304c32.png new file mode 100644 index 0000000000000000000000000000000000000000..a79709ce81b11eed20c3d625cd3e61e773026d82 Binary files /dev/null and b/docs/handson-tl-py/img/12eeeba8-9883-4473-a839-66a9d7304c32.png differ diff --git a/docs/handson-tl-py/img/14342019-e837-4817-89d5-351de82d81f8.png b/docs/handson-tl-py/img/14342019-e837-4817-89d5-351de82d81f8.png new file mode 100644 index 0000000000000000000000000000000000000000..11eee956fbf0000088a6f85c4a86e48ecaa8c0ee Binary files /dev/null and b/docs/handson-tl-py/img/14342019-e837-4817-89d5-351de82d81f8.png differ diff --git a/docs/handson-tl-py/img/14428a54-dea9-4ffd-9a3e-3173cedb5907.png b/docs/handson-tl-py/img/14428a54-dea9-4ffd-9a3e-3173cedb5907.png new file mode 100644 index 0000000000000000000000000000000000000000..e053cf1eb4cfa4b21d3efff086b31d6641453832 Binary files /dev/null and b/docs/handson-tl-py/img/14428a54-dea9-4ffd-9a3e-3173cedb5907.png differ diff --git a/docs/handson-tl-py/img/147cb37b-5b4e-42c8-a1a8-11cd608e32ef.png b/docs/handson-tl-py/img/147cb37b-5b4e-42c8-a1a8-11cd608e32ef.png new file mode 100644 index 0000000000000000000000000000000000000000..edad15a8b1a8fd0f3fad6a6c63e5f2a76390e67a Binary files /dev/null and b/docs/handson-tl-py/img/147cb37b-5b4e-42c8-a1a8-11cd608e32ef.png differ diff --git a/docs/handson-tl-py/img/14c4482c-cab1-4fa7-841f-f1130804fa87.png b/docs/handson-tl-py/img/14c4482c-cab1-4fa7-841f-f1130804fa87.png new file mode 100644 index 0000000000000000000000000000000000000000..188775bba02269269b0e47152518c276077b5c38 Binary files /dev/null and b/docs/handson-tl-py/img/14c4482c-cab1-4fa7-841f-f1130804fa87.png differ diff --git a/docs/handson-tl-py/img/162dc136-f22f-40f3-b991-add0cacaa91a.png b/docs/handson-tl-py/img/162dc136-f22f-40f3-b991-add0cacaa91a.png new file mode 100644 index 0000000000000000000000000000000000000000..ca67177b6f1d08e2680d7a1249acd4f665eff3b8 Binary files /dev/null and b/docs/handson-tl-py/img/162dc136-f22f-40f3-b991-add0cacaa91a.png differ diff --git a/docs/handson-tl-py/img/1682c55a-64fa-4819-8159-98e2f3f4887f.png b/docs/handson-tl-py/img/1682c55a-64fa-4819-8159-98e2f3f4887f.png new file mode 100644 index 0000000000000000000000000000000000000000..31937de361471a0ed71cde0e0f54760ce91c7e56 Binary files /dev/null and b/docs/handson-tl-py/img/1682c55a-64fa-4819-8159-98e2f3f4887f.png differ diff --git a/docs/handson-tl-py/img/17cef5d3-7769-4265-be84-58350fa79917.png b/docs/handson-tl-py/img/17cef5d3-7769-4265-be84-58350fa79917.png new file mode 100644 index 0000000000000000000000000000000000000000..27032866d263570a22df27bbabd171631c888f81 Binary files /dev/null and b/docs/handson-tl-py/img/17cef5d3-7769-4265-be84-58350fa79917.png differ diff --git a/docs/handson-tl-py/img/18baa50d-ad2a-47cf-8b6f-59d223cc0ea7.png b/docs/handson-tl-py/img/18baa50d-ad2a-47cf-8b6f-59d223cc0ea7.png new file mode 100644 index 0000000000000000000000000000000000000000..2e1bfa77c96a93f51c986920bc926ae4988628ae Binary files /dev/null and b/docs/handson-tl-py/img/18baa50d-ad2a-47cf-8b6f-59d223cc0ea7.png differ diff --git a/docs/handson-tl-py/img/1aed3478-51ba-4d72-ab7d-319d0cdf1658.png b/docs/handson-tl-py/img/1aed3478-51ba-4d72-ab7d-319d0cdf1658.png new file mode 100644 index 0000000000000000000000000000000000000000..39bd249ea1623d3eca0b3f626ec49f6a8f6e9e44 Binary files /dev/null and b/docs/handson-tl-py/img/1aed3478-51ba-4d72-ab7d-319d0cdf1658.png differ diff --git a/docs/handson-tl-py/img/1b5b0979-6df5-4b37-ba08-82f68f8c5558.png b/docs/handson-tl-py/img/1b5b0979-6df5-4b37-ba08-82f68f8c5558.png new file mode 100644 index 0000000000000000000000000000000000000000..fa6dc2767d669f9743687935d2a20559fdc99e12 Binary files /dev/null and b/docs/handson-tl-py/img/1b5b0979-6df5-4b37-ba08-82f68f8c5558.png differ diff --git a/docs/handson-tl-py/img/1cb15e3c-28b7-46d1-9475-859ca97ba73b.png b/docs/handson-tl-py/img/1cb15e3c-28b7-46d1-9475-859ca97ba73b.png new file mode 100644 index 0000000000000000000000000000000000000000..bf35be9f6a96b83b06977afabcc76033a8f97fec Binary files /dev/null and b/docs/handson-tl-py/img/1cb15e3c-28b7-46d1-9475-859ca97ba73b.png differ diff --git a/docs/handson-tl-py/img/1e4d609a-d3bb-41ee-8b63-6e137fa2eb4a.png b/docs/handson-tl-py/img/1e4d609a-d3bb-41ee-8b63-6e137fa2eb4a.png new file mode 100644 index 0000000000000000000000000000000000000000..d1385c866a0473a54b3a35cff7cbdc235567ffcc Binary files /dev/null and b/docs/handson-tl-py/img/1e4d609a-d3bb-41ee-8b63-6e137fa2eb4a.png differ diff --git a/docs/handson-tl-py/img/1f3da55c-feb9-4e0a-80b3-65112938b1ab.png b/docs/handson-tl-py/img/1f3da55c-feb9-4e0a-80b3-65112938b1ab.png new file mode 100644 index 0000000000000000000000000000000000000000..f33091d0fc8867e475932097d9d5934f03ff7492 Binary files /dev/null and b/docs/handson-tl-py/img/1f3da55c-feb9-4e0a-80b3-65112938b1ab.png differ diff --git a/docs/handson-tl-py/img/1f407278-f56d-4d09-b2e1-ceb9ea3ad9e5.png b/docs/handson-tl-py/img/1f407278-f56d-4d09-b2e1-ceb9ea3ad9e5.png new file mode 100644 index 0000000000000000000000000000000000000000..9c5377139e2c0cf07de3739c7f261beadaf7326d Binary files /dev/null and b/docs/handson-tl-py/img/1f407278-f56d-4d09-b2e1-ceb9ea3ad9e5.png differ diff --git a/docs/handson-tl-py/img/1f77aa7c-844e-4579-b42b-f5febbdfa09b.png b/docs/handson-tl-py/img/1f77aa7c-844e-4579-b42b-f5febbdfa09b.png new file mode 100644 index 0000000000000000000000000000000000000000..8aa497dca825f3fba3bff5b0402ef55ca658bca8 Binary files /dev/null and b/docs/handson-tl-py/img/1f77aa7c-844e-4579-b42b-f5febbdfa09b.png differ diff --git a/docs/handson-tl-py/img/1fa01356-977d-41e1-ab20-36fab28171bf.png b/docs/handson-tl-py/img/1fa01356-977d-41e1-ab20-36fab28171bf.png new file mode 100644 index 0000000000000000000000000000000000000000..0ef29bc4e449d21320769857c26e0ef92b70e7e2 Binary files /dev/null and b/docs/handson-tl-py/img/1fa01356-977d-41e1-ab20-36fab28171bf.png differ diff --git a/docs/handson-tl-py/img/20a72719-13fb-4588-858b-e498eed2b063.png b/docs/handson-tl-py/img/20a72719-13fb-4588-858b-e498eed2b063.png new file mode 100644 index 0000000000000000000000000000000000000000..50aaeb9e50fa32fc8395cf6e99aaf8215f4a45e5 Binary files /dev/null and b/docs/handson-tl-py/img/20a72719-13fb-4588-858b-e498eed2b063.png differ diff --git a/docs/handson-tl-py/img/2167107a-9494-45ca-8029-30567768daba.png b/docs/handson-tl-py/img/2167107a-9494-45ca-8029-30567768daba.png new file mode 100644 index 0000000000000000000000000000000000000000..828f0e2bd5ebb382bd186c1d4eb1551da90b1472 Binary files /dev/null and b/docs/handson-tl-py/img/2167107a-9494-45ca-8029-30567768daba.png differ diff --git a/docs/handson-tl-py/img/2192626c-f160-4737-ad5a-b96f094ef411.png b/docs/handson-tl-py/img/2192626c-f160-4737-ad5a-b96f094ef411.png new file mode 100644 index 0000000000000000000000000000000000000000..7c80fcb180380b8a3c9cf6a48e5e7dfa4e7ce755 Binary files /dev/null and b/docs/handson-tl-py/img/2192626c-f160-4737-ad5a-b96f094ef411.png differ diff --git a/docs/handson-tl-py/img/219f8a63-cc7a-4657-88da-dd90cc3b01e6.png b/docs/handson-tl-py/img/219f8a63-cc7a-4657-88da-dd90cc3b01e6.png new file mode 100644 index 0000000000000000000000000000000000000000..1a118ef66fd677ca742b5676307f5c941ad71a93 Binary files /dev/null and b/docs/handson-tl-py/img/219f8a63-cc7a-4657-88da-dd90cc3b01e6.png differ diff --git a/docs/handson-tl-py/img/2390e48b-8e9b-415e-a628-158fdda7e2b1.png b/docs/handson-tl-py/img/2390e48b-8e9b-415e-a628-158fdda7e2b1.png new file mode 100644 index 0000000000000000000000000000000000000000..2e8a929d958f6cc5c2c7e3b123014f62987b6b2d Binary files /dev/null and b/docs/handson-tl-py/img/2390e48b-8e9b-415e-a628-158fdda7e2b1.png differ diff --git a/docs/handson-tl-py/img/23e028a0-6c3f-4b96-b9e2-20017877b234.png b/docs/handson-tl-py/img/23e028a0-6c3f-4b96-b9e2-20017877b234.png new file mode 100644 index 0000000000000000000000000000000000000000..393a7f5766436729339f696346728cb9932f280c Binary files /dev/null and b/docs/handson-tl-py/img/23e028a0-6c3f-4b96-b9e2-20017877b234.png differ diff --git a/docs/handson-tl-py/img/25803aa5-92dd-4e45-9711-a1ef2878d940.png b/docs/handson-tl-py/img/25803aa5-92dd-4e45-9711-a1ef2878d940.png new file mode 100644 index 0000000000000000000000000000000000000000..998d366309d5b707d4738ed3c8d5779344643dd1 Binary files /dev/null and b/docs/handson-tl-py/img/25803aa5-92dd-4e45-9711-a1ef2878d940.png differ diff --git a/docs/handson-tl-py/img/259513b0-c48f-487c-898c-780cf7700b29.png b/docs/handson-tl-py/img/259513b0-c48f-487c-898c-780cf7700b29.png new file mode 100644 index 0000000000000000000000000000000000000000..1d58766b67ca3722c67f65a82ad9877bf5d44aeb Binary files /dev/null and b/docs/handson-tl-py/img/259513b0-c48f-487c-898c-780cf7700b29.png differ diff --git a/docs/handson-tl-py/img/25e288db-cbec-4a2b-a1dd-636f85fae3c7.png b/docs/handson-tl-py/img/25e288db-cbec-4a2b-a1dd-636f85fae3c7.png new file mode 100644 index 0000000000000000000000000000000000000000..1ab6648007b823f466ad023edac2f4ec69f44e1f Binary files /dev/null and b/docs/handson-tl-py/img/25e288db-cbec-4a2b-a1dd-636f85fae3c7.png differ diff --git a/docs/handson-tl-py/img/27afcdb0-5ba3-4a05-8278-8ed5112261a8.png b/docs/handson-tl-py/img/27afcdb0-5ba3-4a05-8278-8ed5112261a8.png new file mode 100644 index 0000000000000000000000000000000000000000..b67e4a9f11217ca84603c4ba3d06f245877dc4cd Binary files /dev/null and b/docs/handson-tl-py/img/27afcdb0-5ba3-4a05-8278-8ed5112261a8.png differ diff --git a/docs/handson-tl-py/img/288daaeb-84d1-4175-8929-81e2947101a3.png b/docs/handson-tl-py/img/288daaeb-84d1-4175-8929-81e2947101a3.png new file mode 100644 index 0000000000000000000000000000000000000000..71788f5d4096245fcc1f6a5c552b8f7bfce03465 Binary files /dev/null and b/docs/handson-tl-py/img/288daaeb-84d1-4175-8929-81e2947101a3.png differ diff --git a/docs/handson-tl-py/img/28d6a247-eb12-45c2-a471-8673b8610e63.png b/docs/handson-tl-py/img/28d6a247-eb12-45c2-a471-8673b8610e63.png new file mode 100644 index 0000000000000000000000000000000000000000..3afa160102477351ca20cc223e5c7c1d97bb654c Binary files /dev/null and b/docs/handson-tl-py/img/28d6a247-eb12-45c2-a471-8673b8610e63.png differ diff --git a/docs/handson-tl-py/img/28d6b1a5-598b-4a4b-8941-33a4ee4718dd.png b/docs/handson-tl-py/img/28d6b1a5-598b-4a4b-8941-33a4ee4718dd.png new file mode 100644 index 0000000000000000000000000000000000000000..7114b332bd9a7dad3d614e118b18cd986d042022 Binary files /dev/null and b/docs/handson-tl-py/img/28d6b1a5-598b-4a4b-8941-33a4ee4718dd.png differ diff --git a/docs/handson-tl-py/img/29986810-292e-472d-97a8-f2a71c8ce21d.png b/docs/handson-tl-py/img/29986810-292e-472d-97a8-f2a71c8ce21d.png new file mode 100644 index 0000000000000000000000000000000000000000..22428ee80e986b941374f1c34780b6ef518a61d5 Binary files /dev/null and b/docs/handson-tl-py/img/29986810-292e-472d-97a8-f2a71c8ce21d.png differ diff --git a/docs/handson-tl-py/img/2abf6ed9-391a-4e77-a7d4-5b5757349a19.png b/docs/handson-tl-py/img/2abf6ed9-391a-4e77-a7d4-5b5757349a19.png new file mode 100644 index 0000000000000000000000000000000000000000..e4b254d09467781c16fd8d4f174c8777f3baedaa Binary files /dev/null and b/docs/handson-tl-py/img/2abf6ed9-391a-4e77-a7d4-5b5757349a19.png differ diff --git a/docs/handson-tl-py/img/2ade6f4c-448f-4fad-acd7-2ef73fac4d5e.png b/docs/handson-tl-py/img/2ade6f4c-448f-4fad-acd7-2ef73fac4d5e.png new file mode 100644 index 0000000000000000000000000000000000000000..c7de8e2ef92f6d11e4036dd8f6f06a1b1da1fcd3 Binary files /dev/null and b/docs/handson-tl-py/img/2ade6f4c-448f-4fad-acd7-2ef73fac4d5e.png differ diff --git a/docs/handson-tl-py/img/2b20f0eb-7c5b-42d0-ba6d-a6be88b58247.png b/docs/handson-tl-py/img/2b20f0eb-7c5b-42d0-ba6d-a6be88b58247.png new file mode 100644 index 0000000000000000000000000000000000000000..0646798281086ecfb9a655aa5ce75a46b0e3c222 Binary files /dev/null and b/docs/handson-tl-py/img/2b20f0eb-7c5b-42d0-ba6d-a6be88b58247.png differ diff --git a/docs/handson-tl-py/img/2b447e60-be65-4f11-a9e0-0b28d3c0e730.png b/docs/handson-tl-py/img/2b447e60-be65-4f11-a9e0-0b28d3c0e730.png new file mode 100644 index 0000000000000000000000000000000000000000..b10d6fe9c22c3d016fdbe428cf78803c296c7bc5 Binary files /dev/null and b/docs/handson-tl-py/img/2b447e60-be65-4f11-a9e0-0b28d3c0e730.png differ diff --git a/docs/handson-tl-py/img/2c2a21b9-8c3c-4008-802f-2ed5354d6f91.png b/docs/handson-tl-py/img/2c2a21b9-8c3c-4008-802f-2ed5354d6f91.png new file mode 100644 index 0000000000000000000000000000000000000000..fc24087d55e6edc03869a9fea4ae635d114e40f0 Binary files /dev/null and b/docs/handson-tl-py/img/2c2a21b9-8c3c-4008-802f-2ed5354d6f91.png differ diff --git a/docs/handson-tl-py/img/2fbcbddc-c681-475e-a924-7491c6bcf2a8.png b/docs/handson-tl-py/img/2fbcbddc-c681-475e-a924-7491c6bcf2a8.png new file mode 100644 index 0000000000000000000000000000000000000000..ce388966f42249bdd98e1ff10810c3a0a9397f6a Binary files /dev/null and b/docs/handson-tl-py/img/2fbcbddc-c681-475e-a924-7491c6bcf2a8.png differ diff --git a/docs/handson-tl-py/img/30eca925-b966-4297-9627-8c75345a7cff.png b/docs/handson-tl-py/img/30eca925-b966-4297-9627-8c75345a7cff.png new file mode 100644 index 0000000000000000000000000000000000000000..7c80fcb180380b8a3c9cf6a48e5e7dfa4e7ce755 Binary files /dev/null and b/docs/handson-tl-py/img/30eca925-b966-4297-9627-8c75345a7cff.png differ diff --git a/docs/handson-tl-py/img/31390bf5-05d0-4451-926c-5146b31a240c.png b/docs/handson-tl-py/img/31390bf5-05d0-4451-926c-5146b31a240c.png new file mode 100644 index 0000000000000000000000000000000000000000..489615943f1cf9ac589e25dfa3b4d364845ea3a7 Binary files /dev/null and b/docs/handson-tl-py/img/31390bf5-05d0-4451-926c-5146b31a240c.png differ diff --git a/docs/handson-tl-py/img/323c9d93-2e26-419b-9ed4-16099e6a5350.png b/docs/handson-tl-py/img/323c9d93-2e26-419b-9ed4-16099e6a5350.png new file mode 100644 index 0000000000000000000000000000000000000000..57e4547e2a6cd261ff0f378a38f37fcb1e9a8ba6 Binary files /dev/null and b/docs/handson-tl-py/img/323c9d93-2e26-419b-9ed4-16099e6a5350.png differ diff --git a/docs/handson-tl-py/img/32d3a718-5dc8-4ced-a248-bf868068ea3c.png b/docs/handson-tl-py/img/32d3a718-5dc8-4ced-a248-bf868068ea3c.png new file mode 100644 index 0000000000000000000000000000000000000000..e02b6e64129b5b057e4f5c7857cbb84840e9207d Binary files /dev/null and b/docs/handson-tl-py/img/32d3a718-5dc8-4ced-a248-bf868068ea3c.png differ diff --git a/docs/handson-tl-py/img/3375f65f-4548-482c-be23-d5047db6232d.png b/docs/handson-tl-py/img/3375f65f-4548-482c-be23-d5047db6232d.png new file mode 100644 index 0000000000000000000000000000000000000000..0ef689708553f9b15f70061596a1a39263ebbc80 Binary files /dev/null and b/docs/handson-tl-py/img/3375f65f-4548-482c-be23-d5047db6232d.png differ diff --git a/docs/handson-tl-py/img/3401841c-2937-4b3b-9e6a-d565df408d9e.png b/docs/handson-tl-py/img/3401841c-2937-4b3b-9e6a-d565df408d9e.png new file mode 100644 index 0000000000000000000000000000000000000000..ad9276c78e5c4195aa86085cc76302b06c3378b5 Binary files /dev/null and b/docs/handson-tl-py/img/3401841c-2937-4b3b-9e6a-d565df408d9e.png differ diff --git a/docs/handson-tl-py/img/35710e6e-1f70-4ab8-9727-9c318f0e0d4f.png b/docs/handson-tl-py/img/35710e6e-1f70-4ab8-9727-9c318f0e0d4f.png new file mode 100644 index 0000000000000000000000000000000000000000..ea682a3673b8505466c8c3c6de3f9a45fcc8e639 Binary files /dev/null and b/docs/handson-tl-py/img/35710e6e-1f70-4ab8-9727-9c318f0e0d4f.png differ diff --git a/docs/handson-tl-py/img/35852910-b11e-45b5-a94f-f0cf8df4d9ac.png b/docs/handson-tl-py/img/35852910-b11e-45b5-a94f-f0cf8df4d9ac.png new file mode 100644 index 0000000000000000000000000000000000000000..a6baa4846d8e4ac672ea5b6d562b6d20a3c3d6b4 Binary files /dev/null and b/docs/handson-tl-py/img/35852910-b11e-45b5-a94f-f0cf8df4d9ac.png differ diff --git a/docs/handson-tl-py/img/3657cb60-4e28-4e83-bd5b-324637f867ad.png b/docs/handson-tl-py/img/3657cb60-4e28-4e83-bd5b-324637f867ad.png new file mode 100644 index 0000000000000000000000000000000000000000..aae6f6be881066ca5468014ade5ac7dc37f5b1c0 Binary files /dev/null and b/docs/handson-tl-py/img/3657cb60-4e28-4e83-bd5b-324637f867ad.png differ diff --git a/docs/handson-tl-py/img/368145b5-993b-4ece-acc2-74dfb213a8bc.png b/docs/handson-tl-py/img/368145b5-993b-4ece-acc2-74dfb213a8bc.png new file mode 100644 index 0000000000000000000000000000000000000000..3d05b4a52947fa208dd1d0a123328216dbc535e3 Binary files /dev/null and b/docs/handson-tl-py/img/368145b5-993b-4ece-acc2-74dfb213a8bc.png differ diff --git a/docs/handson-tl-py/img/36eaace3-e9a1-4cd0-af03-7f709279986a.png b/docs/handson-tl-py/img/36eaace3-e9a1-4cd0-af03-7f709279986a.png new file mode 100644 index 0000000000000000000000000000000000000000..edad15a8b1a8fd0f3fad6a6c63e5f2a76390e67a Binary files /dev/null and b/docs/handson-tl-py/img/36eaace3-e9a1-4cd0-af03-7f709279986a.png differ diff --git a/docs/handson-tl-py/img/37049eb5-b79c-43bf-b8b1-fceb07a6697c.png b/docs/handson-tl-py/img/37049eb5-b79c-43bf-b8b1-fceb07a6697c.png new file mode 100644 index 0000000000000000000000000000000000000000..d28c2eda8fdad6a85d504c43c96de7efde1177cd Binary files /dev/null and b/docs/handson-tl-py/img/37049eb5-b79c-43bf-b8b1-fceb07a6697c.png differ diff --git a/docs/handson-tl-py/img/37a13513-298a-414a-beeb-76c68f9c1a36.png b/docs/handson-tl-py/img/37a13513-298a-414a-beeb-76c68f9c1a36.png new file mode 100644 index 0000000000000000000000000000000000000000..05a5b5aa863c816f014b88b8d2f68e1927a9e2bf Binary files /dev/null and b/docs/handson-tl-py/img/37a13513-298a-414a-beeb-76c68f9c1a36.png differ diff --git a/docs/handson-tl-py/img/37f6f397-2a16-407c-be9f-22e1ff9df205.png b/docs/handson-tl-py/img/37f6f397-2a16-407c-be9f-22e1ff9df205.png new file mode 100644 index 0000000000000000000000000000000000000000..b3ef3fd815427dc7c383d6f2a51deb959cb81a44 Binary files /dev/null and b/docs/handson-tl-py/img/37f6f397-2a16-407c-be9f-22e1ff9df205.png differ diff --git a/docs/handson-tl-py/img/38d54157-0b1a-4a50-bced-6460281a2b6f.png b/docs/handson-tl-py/img/38d54157-0b1a-4a50-bced-6460281a2b6f.png new file mode 100644 index 0000000000000000000000000000000000000000..53add7d370d94d8e7e428befed901697ec14793b Binary files /dev/null and b/docs/handson-tl-py/img/38d54157-0b1a-4a50-bced-6460281a2b6f.png differ diff --git a/docs/handson-tl-py/img/396956bf-0eda-471a-a398-3e739b37e214.png b/docs/handson-tl-py/img/396956bf-0eda-471a-a398-3e739b37e214.png new file mode 100644 index 0000000000000000000000000000000000000000..3b17e06f31f5d6c199ca442800373cc7f33ffb3f Binary files /dev/null and b/docs/handson-tl-py/img/396956bf-0eda-471a-a398-3e739b37e214.png differ diff --git a/docs/handson-tl-py/img/39e61d86-4db6-460c-a13b-8da4dbcd4889.png b/docs/handson-tl-py/img/39e61d86-4db6-460c-a13b-8da4dbcd4889.png new file mode 100644 index 0000000000000000000000000000000000000000..1e826eb9213a462b14bbfb9da428eb9e578e5258 Binary files /dev/null and b/docs/handson-tl-py/img/39e61d86-4db6-460c-a13b-8da4dbcd4889.png differ diff --git a/docs/handson-tl-py/img/3a4330e8-8f0c-440a-aa0c-b0faf5f172d9.png b/docs/handson-tl-py/img/3a4330e8-8f0c-440a-aa0c-b0faf5f172d9.png new file mode 100644 index 0000000000000000000000000000000000000000..62465f280fa601fa200f4d36f96c0d210af97ce0 Binary files /dev/null and b/docs/handson-tl-py/img/3a4330e8-8f0c-440a-aa0c-b0faf5f172d9.png differ diff --git a/docs/handson-tl-py/img/3a4d72af-af1e-4c54-810d-6190c28141c9.png b/docs/handson-tl-py/img/3a4d72af-af1e-4c54-810d-6190c28141c9.png new file mode 100644 index 0000000000000000000000000000000000000000..9ea7163747ccdd72a334ad7020a7c106ba863733 Binary files /dev/null and b/docs/handson-tl-py/img/3a4d72af-af1e-4c54-810d-6190c28141c9.png differ diff --git a/docs/handson-tl-py/img/3a9b6edb-d7da-4cc8-9007-9ebd67ed7f35.png b/docs/handson-tl-py/img/3a9b6edb-d7da-4cc8-9007-9ebd67ed7f35.png new file mode 100644 index 0000000000000000000000000000000000000000..4fefdda1eef2d354e957790912c9f2d17e809b7b Binary files /dev/null and b/docs/handson-tl-py/img/3a9b6edb-d7da-4cc8-9007-9ebd67ed7f35.png differ diff --git a/docs/handson-tl-py/img/3cf0fe3d-bbc6-41cb-ac42-589a9a9d8c53.png b/docs/handson-tl-py/img/3cf0fe3d-bbc6-41cb-ac42-589a9a9d8c53.png new file mode 100644 index 0000000000000000000000000000000000000000..fab812a789e57c6c00f97729ac54d832513803b5 Binary files /dev/null and b/docs/handson-tl-py/img/3cf0fe3d-bbc6-41cb-ac42-589a9a9d8c53.png differ diff --git a/docs/handson-tl-py/img/3d3057b8-e22d-424c-8c47-ce16823d1d67.png b/docs/handson-tl-py/img/3d3057b8-e22d-424c-8c47-ce16823d1d67.png new file mode 100644 index 0000000000000000000000000000000000000000..bdacebcd85ba27d30b6d39709dbace5190c5cc06 Binary files /dev/null and b/docs/handson-tl-py/img/3d3057b8-e22d-424c-8c47-ce16823d1d67.png differ diff --git a/docs/handson-tl-py/img/3e35f31d-36ad-4097-aa25-7dcd5b0fe6ad.png b/docs/handson-tl-py/img/3e35f31d-36ad-4097-aa25-7dcd5b0fe6ad.png new file mode 100644 index 0000000000000000000000000000000000000000..7fb031f58378c89c3cfd086fb153806f59dbf2e0 Binary files /dev/null and b/docs/handson-tl-py/img/3e35f31d-36ad-4097-aa25-7dcd5b0fe6ad.png differ diff --git a/docs/handson-tl-py/img/3e5c28bf-979d-492c-afea-53f995e924d7.png b/docs/handson-tl-py/img/3e5c28bf-979d-492c-afea-53f995e924d7.png new file mode 100644 index 0000000000000000000000000000000000000000..30d0e2eb8fa4de9ff4b8742f15a15d6018627440 Binary files /dev/null and b/docs/handson-tl-py/img/3e5c28bf-979d-492c-afea-53f995e924d7.png differ diff --git a/docs/handson-tl-py/img/3ea03e05-e414-414b-a22f-a33b8cb39f64.png b/docs/handson-tl-py/img/3ea03e05-e414-414b-a22f-a33b8cb39f64.png new file mode 100644 index 0000000000000000000000000000000000000000..0625dcf52a3fddb0599cdad5de447348e6f1cae9 Binary files /dev/null and b/docs/handson-tl-py/img/3ea03e05-e414-414b-a22f-a33b8cb39f64.png differ diff --git a/docs/handson-tl-py/img/3f0bcdb3-568a-48e2-8003-b2cd4f4f4b61.png b/docs/handson-tl-py/img/3f0bcdb3-568a-48e2-8003-b2cd4f4f4b61.png new file mode 100644 index 0000000000000000000000000000000000000000..24edf1b5f6159259525eee1a78bf571ca0be66ea Binary files /dev/null and b/docs/handson-tl-py/img/3f0bcdb3-568a-48e2-8003-b2cd4f4f4b61.png differ diff --git a/docs/handson-tl-py/img/3f298afe-2285-46bb-84e2-53405817ffbb.png b/docs/handson-tl-py/img/3f298afe-2285-46bb-84e2-53405817ffbb.png new file mode 100644 index 0000000000000000000000000000000000000000..757bc46003e05e380ac770f44e218ee2315fe9e5 Binary files /dev/null and b/docs/handson-tl-py/img/3f298afe-2285-46bb-84e2-53405817ffbb.png differ diff --git a/docs/handson-tl-py/img/3f400373-77e3-4fbe-8655-b312ddf67dc5.png b/docs/handson-tl-py/img/3f400373-77e3-4fbe-8655-b312ddf67dc5.png new file mode 100644 index 0000000000000000000000000000000000000000..0a79ef54e28c75651edda88756f4954fae3e3421 Binary files /dev/null and b/docs/handson-tl-py/img/3f400373-77e3-4fbe-8655-b312ddf67dc5.png differ diff --git a/docs/handson-tl-py/img/3fadb37c-895c-49fa-b6ad-3239d4c43071.png b/docs/handson-tl-py/img/3fadb37c-895c-49fa-b6ad-3239d4c43071.png new file mode 100644 index 0000000000000000000000000000000000000000..4f0b509b16ba503ecd5e83b7eaf70b006efd2976 Binary files /dev/null and b/docs/handson-tl-py/img/3fadb37c-895c-49fa-b6ad-3239d4c43071.png differ diff --git a/docs/handson-tl-py/img/3fe6be82-124d-4f5d-9594-db0a4b4c22af.png b/docs/handson-tl-py/img/3fe6be82-124d-4f5d-9594-db0a4b4c22af.png new file mode 100644 index 0000000000000000000000000000000000000000..46e00158b5e93daef2de9fa38b0bbdbcfca87138 Binary files /dev/null and b/docs/handson-tl-py/img/3fe6be82-124d-4f5d-9594-db0a4b4c22af.png differ diff --git a/docs/handson-tl-py/img/42d93da8-d1e5-45c3-9c03-83ace0fcf7d7.png b/docs/handson-tl-py/img/42d93da8-d1e5-45c3-9c03-83ace0fcf7d7.png new file mode 100644 index 0000000000000000000000000000000000000000..36a7af2d3f0cf2b01d87fc92d672a1aec6ae46eb Binary files /dev/null and b/docs/handson-tl-py/img/42d93da8-d1e5-45c3-9c03-83ace0fcf7d7.png differ diff --git a/docs/handson-tl-py/img/437e289f-8d33-4ea2-aafe-6c6113edfeba.png b/docs/handson-tl-py/img/437e289f-8d33-4ea2-aafe-6c6113edfeba.png new file mode 100644 index 0000000000000000000000000000000000000000..08198db9d871b12b5961532a2b79982b147fe89c Binary files /dev/null and b/docs/handson-tl-py/img/437e289f-8d33-4ea2-aafe-6c6113edfeba.png differ diff --git a/docs/handson-tl-py/img/4411249b-c6c4-4faf-bb66-9598ca848e6c.png b/docs/handson-tl-py/img/4411249b-c6c4-4faf-bb66-9598ca848e6c.png new file mode 100644 index 0000000000000000000000000000000000000000..12be6b40dad86d346ab220bbe704732b98e314dc Binary files /dev/null and b/docs/handson-tl-py/img/4411249b-c6c4-4faf-bb66-9598ca848e6c.png differ diff --git a/docs/handson-tl-py/img/4429ffc8-edae-4456-be09-62b672c01130.png b/docs/handson-tl-py/img/4429ffc8-edae-4456-be09-62b672c01130.png new file mode 100644 index 0000000000000000000000000000000000000000..160a814df393b304eabc4d888e95c6d5ae208703 Binary files /dev/null and b/docs/handson-tl-py/img/4429ffc8-edae-4456-be09-62b672c01130.png differ diff --git a/docs/handson-tl-py/img/4654a29a-6aef-4c89-8ad1-e7dc98dbb64f.png b/docs/handson-tl-py/img/4654a29a-6aef-4c89-8ad1-e7dc98dbb64f.png new file mode 100644 index 0000000000000000000000000000000000000000..a3959299ac702ff15afdc60944ea2f8e375caa72 Binary files /dev/null and b/docs/handson-tl-py/img/4654a29a-6aef-4c89-8ad1-e7dc98dbb64f.png differ diff --git a/docs/handson-tl-py/img/46fda9c6-12cf-4933-9a8a-14d4eb2bd6ec.png b/docs/handson-tl-py/img/46fda9c6-12cf-4933-9a8a-14d4eb2bd6ec.png new file mode 100644 index 0000000000000000000000000000000000000000..78f58c716bce649fa5b08b9d1c2d5bef958c22e9 Binary files /dev/null and b/docs/handson-tl-py/img/46fda9c6-12cf-4933-9a8a-14d4eb2bd6ec.png differ diff --git a/docs/handson-tl-py/img/47410ec4-28c7-4e91-9152-23363f957e63.png b/docs/handson-tl-py/img/47410ec4-28c7-4e91-9152-23363f957e63.png new file mode 100644 index 0000000000000000000000000000000000000000..8b6c10b41bddcae8ed971abdf6f631fa211c1aa9 Binary files /dev/null and b/docs/handson-tl-py/img/47410ec4-28c7-4e91-9152-23363f957e63.png differ diff --git a/docs/handson-tl-py/img/47937375-d105-4389-89ce-20b30cfda764.png b/docs/handson-tl-py/img/47937375-d105-4389-89ce-20b30cfda764.png new file mode 100644 index 0000000000000000000000000000000000000000..052823ebfb6e5736278d341e44bbb354df2b61f3 Binary files /dev/null and b/docs/handson-tl-py/img/47937375-d105-4389-89ce-20b30cfda764.png differ diff --git a/docs/handson-tl-py/img/4801ec05-89b1-43d2-ad9f-32b0d98b140e.png b/docs/handson-tl-py/img/4801ec05-89b1-43d2-ad9f-32b0d98b140e.png new file mode 100644 index 0000000000000000000000000000000000000000..b37cb58466ee3ddf6a1d6840dc83357f6d390b21 Binary files /dev/null and b/docs/handson-tl-py/img/4801ec05-89b1-43d2-ad9f-32b0d98b140e.png differ diff --git a/docs/handson-tl-py/img/480965bb-c9e2-42b1-9b06-0003135d17a1.png b/docs/handson-tl-py/img/480965bb-c9e2-42b1-9b06-0003135d17a1.png new file mode 100644 index 0000000000000000000000000000000000000000..9d26a10d3112e98bda3408aff463113aa953270a Binary files /dev/null and b/docs/handson-tl-py/img/480965bb-c9e2-42b1-9b06-0003135d17a1.png differ diff --git a/docs/handson-tl-py/img/482af651-725c-47b7-916b-3e2347490314.png b/docs/handson-tl-py/img/482af651-725c-47b7-916b-3e2347490314.png new file mode 100644 index 0000000000000000000000000000000000000000..6b9c5edc8f9433b20390ac553c69d0da49268773 Binary files /dev/null and b/docs/handson-tl-py/img/482af651-725c-47b7-916b-3e2347490314.png differ diff --git a/docs/handson-tl-py/img/484ebcb5-b14e-4c76-945e-b6562391ba5f.png b/docs/handson-tl-py/img/484ebcb5-b14e-4c76-945e-b6562391ba5f.png new file mode 100644 index 0000000000000000000000000000000000000000..5ef0fbaa5f2558d53bba91a06756e9bdeabbfa6e Binary files /dev/null and b/docs/handson-tl-py/img/484ebcb5-b14e-4c76-945e-b6562391ba5f.png differ diff --git a/docs/handson-tl-py/img/4931835b-36f1-4802-8af9-b2163f218c48.png b/docs/handson-tl-py/img/4931835b-36f1-4802-8af9-b2163f218c48.png new file mode 100644 index 0000000000000000000000000000000000000000..1b4ba8993d42fa4280ecc207d6a568eb76c44d88 Binary files /dev/null and b/docs/handson-tl-py/img/4931835b-36f1-4802-8af9-b2163f218c48.png differ diff --git a/docs/handson-tl-py/img/496b8ebd-2671-47eb-9222-c5ba6849f120.png b/docs/handson-tl-py/img/496b8ebd-2671-47eb-9222-c5ba6849f120.png new file mode 100644 index 0000000000000000000000000000000000000000..a7b1d2d9a4c5c869c964f75c4e55bd0d87df4a72 Binary files /dev/null and b/docs/handson-tl-py/img/496b8ebd-2671-47eb-9222-c5ba6849f120.png differ diff --git a/docs/handson-tl-py/img/499881f7-60ac-48f4-acdc-9b0b63f1ec1d.png b/docs/handson-tl-py/img/499881f7-60ac-48f4-acdc-9b0b63f1ec1d.png new file mode 100644 index 0000000000000000000000000000000000000000..5720833ed43eb717a58cd415ee54a439435e0f17 Binary files /dev/null and b/docs/handson-tl-py/img/499881f7-60ac-48f4-acdc-9b0b63f1ec1d.png differ diff --git a/docs/handson-tl-py/img/4bcb6159-c17e-40de-9a72-0136cecd72f3.png b/docs/handson-tl-py/img/4bcb6159-c17e-40de-9a72-0136cecd72f3.png new file mode 100644 index 0000000000000000000000000000000000000000..44580a0ab81c53307faaaa8cf4370bdeb5b42978 Binary files /dev/null and b/docs/handson-tl-py/img/4bcb6159-c17e-40de-9a72-0136cecd72f3.png differ diff --git a/docs/handson-tl-py/img/4c391b2d-6a89-4c72-93f0-458c55e7e3b6.png b/docs/handson-tl-py/img/4c391b2d-6a89-4c72-93f0-458c55e7e3b6.png new file mode 100644 index 0000000000000000000000000000000000000000..20fa299432f8dcf635fe6cf3e3036c47f6f64f42 Binary files /dev/null and b/docs/handson-tl-py/img/4c391b2d-6a89-4c72-93f0-458c55e7e3b6.png differ diff --git a/docs/handson-tl-py/img/4dd47053-3b9d-460f-8bd7-cf9137534599.png b/docs/handson-tl-py/img/4dd47053-3b9d-460f-8bd7-cf9137534599.png new file mode 100644 index 0000000000000000000000000000000000000000..795178f1aea06606859d0311ab6c9743ac7a2bd0 Binary files /dev/null and b/docs/handson-tl-py/img/4dd47053-3b9d-460f-8bd7-cf9137534599.png differ diff --git a/docs/handson-tl-py/img/4ddc9f0b-c283-4a72-9649-82b5ae997c49.png b/docs/handson-tl-py/img/4ddc9f0b-c283-4a72-9649-82b5ae997c49.png new file mode 100644 index 0000000000000000000000000000000000000000..4733960556879a2c115799246c2bff7faae4ac54 Binary files /dev/null and b/docs/handson-tl-py/img/4ddc9f0b-c283-4a72-9649-82b5ae997c49.png differ diff --git a/docs/handson-tl-py/img/4dddc3e3-867d-4cae-b6aa-385758c54f6c.png b/docs/handson-tl-py/img/4dddc3e3-867d-4cae-b6aa-385758c54f6c.png new file mode 100644 index 0000000000000000000000000000000000000000..291dd0a8571759538cd7a9bb5270046f91949421 Binary files /dev/null and b/docs/handson-tl-py/img/4dddc3e3-867d-4cae-b6aa-385758c54f6c.png differ diff --git a/docs/handson-tl-py/img/4dff20d1-9253-415f-8a18-62f217d9ddcc.png b/docs/handson-tl-py/img/4dff20d1-9253-415f-8a18-62f217d9ddcc.png new file mode 100644 index 0000000000000000000000000000000000000000..d03d9f0ca44b6e2ede79813bc01d2cb33c3e7539 Binary files /dev/null and b/docs/handson-tl-py/img/4dff20d1-9253-415f-8a18-62f217d9ddcc.png differ diff --git a/docs/handson-tl-py/img/4e505ad7-0de3-474e-b874-95c380aec9b5.png b/docs/handson-tl-py/img/4e505ad7-0de3-474e-b874-95c380aec9b5.png new file mode 100644 index 0000000000000000000000000000000000000000..1d598bf1f83fbd49a5f48da3e969eaa729c7514f Binary files /dev/null and b/docs/handson-tl-py/img/4e505ad7-0de3-474e-b874-95c380aec9b5.png differ diff --git a/docs/handson-tl-py/img/4e95a622-a125-4518-9d81-f0de283fe145.png b/docs/handson-tl-py/img/4e95a622-a125-4518-9d81-f0de283fe145.png new file mode 100644 index 0000000000000000000000000000000000000000..24879da9be9e327770cf5c9f42d4fb995106ef57 Binary files /dev/null and b/docs/handson-tl-py/img/4e95a622-a125-4518-9d81-f0de283fe145.png differ diff --git a/docs/handson-tl-py/img/504adb56-097d-44d0-ac06-2d84988b996c.png b/docs/handson-tl-py/img/504adb56-097d-44d0-ac06-2d84988b996c.png new file mode 100644 index 0000000000000000000000000000000000000000..baacce67edc05f099c2c984c568639f42518c698 Binary files /dev/null and b/docs/handson-tl-py/img/504adb56-097d-44d0-ac06-2d84988b996c.png differ diff --git a/docs/handson-tl-py/img/504e31b7-bbd1-4ae6-ae1c-a09536802838.png b/docs/handson-tl-py/img/504e31b7-bbd1-4ae6-ae1c-a09536802838.png new file mode 100644 index 0000000000000000000000000000000000000000..3ba65ea0459d690d7366ce554deb3b2ba7af865b Binary files /dev/null and b/docs/handson-tl-py/img/504e31b7-bbd1-4ae6-ae1c-a09536802838.png differ diff --git a/docs/handson-tl-py/img/5109e69b-4a8d-42ea-995e-728f0130f11d.png b/docs/handson-tl-py/img/5109e69b-4a8d-42ea-995e-728f0130f11d.png new file mode 100644 index 0000000000000000000000000000000000000000..782e2f1ab7204c55294bde4b129ed14bfaf737ed Binary files /dev/null and b/docs/handson-tl-py/img/5109e69b-4a8d-42ea-995e-728f0130f11d.png differ diff --git a/docs/handson-tl-py/img/51b9fe99-1a68-45a1-87b6-3c24c4ce603c.png b/docs/handson-tl-py/img/51b9fe99-1a68-45a1-87b6-3c24c4ce603c.png new file mode 100644 index 0000000000000000000000000000000000000000..39d8d5dae2b1bb0ac7f19a4fc0e1bfc6e87621ec Binary files /dev/null and b/docs/handson-tl-py/img/51b9fe99-1a68-45a1-87b6-3c24c4ce603c.png differ diff --git a/docs/handson-tl-py/img/52fe1877-dded-4161-8520-ba0d425f2452.png b/docs/handson-tl-py/img/52fe1877-dded-4161-8520-ba0d425f2452.png new file mode 100644 index 0000000000000000000000000000000000000000..8c56858e02718d95182d9be67aac9a91f73e1958 Binary files /dev/null and b/docs/handson-tl-py/img/52fe1877-dded-4161-8520-ba0d425f2452.png differ diff --git a/docs/handson-tl-py/img/55302513-01d7-4f93-a61a-072c9d88ed30.png b/docs/handson-tl-py/img/55302513-01d7-4f93-a61a-072c9d88ed30.png new file mode 100644 index 0000000000000000000000000000000000000000..7c80fcb180380b8a3c9cf6a48e5e7dfa4e7ce755 Binary files /dev/null and b/docs/handson-tl-py/img/55302513-01d7-4f93-a61a-072c9d88ed30.png differ diff --git a/docs/handson-tl-py/img/55556fe2-bd04-47ea-a9c6-9660475b4950.png b/docs/handson-tl-py/img/55556fe2-bd04-47ea-a9c6-9660475b4950.png new file mode 100644 index 0000000000000000000000000000000000000000..20bbaef0ee2dca95533ed94bf644d76fa58493b6 Binary files /dev/null and b/docs/handson-tl-py/img/55556fe2-bd04-47ea-a9c6-9660475b4950.png differ diff --git a/docs/handson-tl-py/img/55fd42ef-4c13-4489-9347-c211bf44fb7c.png b/docs/handson-tl-py/img/55fd42ef-4c13-4489-9347-c211bf44fb7c.png new file mode 100644 index 0000000000000000000000000000000000000000..add326296b56fc22a213e90e5b0e1de1b275da86 Binary files /dev/null and b/docs/handson-tl-py/img/55fd42ef-4c13-4489-9347-c211bf44fb7c.png differ diff --git a/docs/handson-tl-py/img/572ea417-a601-4d49-b75c-24e4f2f6a6e0.png b/docs/handson-tl-py/img/572ea417-a601-4d49-b75c-24e4f2f6a6e0.png new file mode 100644 index 0000000000000000000000000000000000000000..ab1a9776247d46a7c4e702a880e3ae81651127d8 Binary files /dev/null and b/docs/handson-tl-py/img/572ea417-a601-4d49-b75c-24e4f2f6a6e0.png differ diff --git a/docs/handson-tl-py/img/58ca16d2-f5bc-48dc-8e18-44aa17afaa25.png b/docs/handson-tl-py/img/58ca16d2-f5bc-48dc-8e18-44aa17afaa25.png new file mode 100644 index 0000000000000000000000000000000000000000..93df2c7dc211696bce8812b40ac8dca172720f1a Binary files /dev/null and b/docs/handson-tl-py/img/58ca16d2-f5bc-48dc-8e18-44aa17afaa25.png differ diff --git a/docs/handson-tl-py/img/5a131554-b82b-40b6-8406-a0f8e1f1da7b.jpg b/docs/handson-tl-py/img/5a131554-b82b-40b6-8406-a0f8e1f1da7b.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7f5f0575e922d8da6e3549b776d742c8d81a8e5a Binary files /dev/null and b/docs/handson-tl-py/img/5a131554-b82b-40b6-8406-a0f8e1f1da7b.jpg differ diff --git a/docs/handson-tl-py/img/5b7fc827-b620-41f8-b1d3-6a35eacfc45e.png b/docs/handson-tl-py/img/5b7fc827-b620-41f8-b1d3-6a35eacfc45e.png new file mode 100644 index 0000000000000000000000000000000000000000..a9da4595aeebb96defd74890c3bd429cc7d78ec5 Binary files /dev/null and b/docs/handson-tl-py/img/5b7fc827-b620-41f8-b1d3-6a35eacfc45e.png differ diff --git a/docs/handson-tl-py/img/5c23c579-af88-4668-90a6-ae5faf899502.png b/docs/handson-tl-py/img/5c23c579-af88-4668-90a6-ae5faf899502.png new file mode 100644 index 0000000000000000000000000000000000000000..1bdf79a448173edd78a5b44fa51dd936a35116f2 Binary files /dev/null and b/docs/handson-tl-py/img/5c23c579-af88-4668-90a6-ae5faf899502.png differ diff --git a/docs/handson-tl-py/img/5da17bd5-6922-48a9-b779-3cfaff2c2a45.png b/docs/handson-tl-py/img/5da17bd5-6922-48a9-b779-3cfaff2c2a45.png new file mode 100644 index 0000000000000000000000000000000000000000..ec8071a9088354cbf300ffbab5e1cd888c37340e Binary files /dev/null and b/docs/handson-tl-py/img/5da17bd5-6922-48a9-b779-3cfaff2c2a45.png differ diff --git a/docs/handson-tl-py/img/5dda55cb-a1a3-4ce4-a112-ec0d16c5466f.png b/docs/handson-tl-py/img/5dda55cb-a1a3-4ce4-a112-ec0d16c5466f.png new file mode 100644 index 0000000000000000000000000000000000000000..b1fd5e56ed822ad0f3ed8d355f410e9a8a19f36e Binary files /dev/null and b/docs/handson-tl-py/img/5dda55cb-a1a3-4ce4-a112-ec0d16c5466f.png differ diff --git a/docs/handson-tl-py/img/5e29eb62-35f0-419c-923a-e6d79eb07ca8.png b/docs/handson-tl-py/img/5e29eb62-35f0-419c-923a-e6d79eb07ca8.png new file mode 100644 index 0000000000000000000000000000000000000000..11e9df8d9116a4158542f9fa89a1f984b388d4b5 Binary files /dev/null and b/docs/handson-tl-py/img/5e29eb62-35f0-419c-923a-e6d79eb07ca8.png differ diff --git a/docs/handson-tl-py/img/6174260e-2892-4fe2-9cb9-a687760ee2a9.png b/docs/handson-tl-py/img/6174260e-2892-4fe2-9cb9-a687760ee2a9.png new file mode 100644 index 0000000000000000000000000000000000000000..3454ad3be23ce68bed1ac97346b6021760cb1bc2 Binary files /dev/null and b/docs/handson-tl-py/img/6174260e-2892-4fe2-9cb9-a687760ee2a9.png differ diff --git a/docs/handson-tl-py/img/6652c604-0dbe-4ce6-abe2-979daa464c24.png b/docs/handson-tl-py/img/6652c604-0dbe-4ce6-abe2-979daa464c24.png new file mode 100644 index 0000000000000000000000000000000000000000..c958524da0506f3655358859aca9cfc7b58b229b Binary files /dev/null and b/docs/handson-tl-py/img/6652c604-0dbe-4ce6-abe2-979daa464c24.png differ diff --git a/docs/handson-tl-py/img/66a126af-3f84-48bf-bba6-b93cef70aa7e.png b/docs/handson-tl-py/img/66a126af-3f84-48bf-bba6-b93cef70aa7e.png new file mode 100644 index 0000000000000000000000000000000000000000..6e7a7711c9cffd62c0ad057a178469c9ce57c39b Binary files /dev/null and b/docs/handson-tl-py/img/66a126af-3f84-48bf-bba6-b93cef70aa7e.png differ diff --git a/docs/handson-tl-py/img/66db5895-f2ec-4bb1-94b7-b5dad17f5604.png b/docs/handson-tl-py/img/66db5895-f2ec-4bb1-94b7-b5dad17f5604.png new file mode 100644 index 0000000000000000000000000000000000000000..b0f278e31b40902bab81001b3c0a68540fa2548b Binary files /dev/null and b/docs/handson-tl-py/img/66db5895-f2ec-4bb1-94b7-b5dad17f5604.png differ diff --git a/docs/handson-tl-py/img/67da829c-5832-4cb4-8d09-fe4617f87e1c.png b/docs/handson-tl-py/img/67da829c-5832-4cb4-8d09-fe4617f87e1c.png new file mode 100644 index 0000000000000000000000000000000000000000..14a34a4dd3ae0cbe04d69303f6d6bc8f6d1a59b3 Binary files /dev/null and b/docs/handson-tl-py/img/67da829c-5832-4cb4-8d09-fe4617f87e1c.png differ diff --git a/docs/handson-tl-py/img/6be810e1-6824-4697-a3f4-46705e71d681.png b/docs/handson-tl-py/img/6be810e1-6824-4697-a3f4-46705e71d681.png new file mode 100644 index 0000000000000000000000000000000000000000..237948044999ac2c11b9624774bdc7f270678714 Binary files /dev/null and b/docs/handson-tl-py/img/6be810e1-6824-4697-a3f4-46705e71d681.png differ diff --git a/docs/handson-tl-py/img/6c6f66d8-35b7-442b-ae4a-3d7c019b7c63.png b/docs/handson-tl-py/img/6c6f66d8-35b7-442b-ae4a-3d7c019b7c63.png new file mode 100644 index 0000000000000000000000000000000000000000..144ed2a6f21d5aeeb6eb54e7ff98cc5b1be56935 Binary files /dev/null and b/docs/handson-tl-py/img/6c6f66d8-35b7-442b-ae4a-3d7c019b7c63.png differ diff --git a/docs/handson-tl-py/img/6e0e4adc-92fd-419f-8809-aca43ca684e1.png b/docs/handson-tl-py/img/6e0e4adc-92fd-419f-8809-aca43ca684e1.png new file mode 100644 index 0000000000000000000000000000000000000000..7c80fcb180380b8a3c9cf6a48e5e7dfa4e7ce755 Binary files /dev/null and b/docs/handson-tl-py/img/6e0e4adc-92fd-419f-8809-aca43ca684e1.png differ diff --git a/docs/handson-tl-py/img/6e193609-867e-44a1-a8ca-bcd3b07b2940.png b/docs/handson-tl-py/img/6e193609-867e-44a1-a8ca-bcd3b07b2940.png new file mode 100644 index 0000000000000000000000000000000000000000..f1427d5e455bce47edff314fd87a5e08dc149c0f Binary files /dev/null and b/docs/handson-tl-py/img/6e193609-867e-44a1-a8ca-bcd3b07b2940.png differ diff --git a/docs/handson-tl-py/img/6e1f5245-8381-41b4-8de3-f0ad95f59569.png b/docs/handson-tl-py/img/6e1f5245-8381-41b4-8de3-f0ad95f59569.png new file mode 100644 index 0000000000000000000000000000000000000000..48e13f6481e237a242c81ce7f51bcb4ed66bc43f Binary files /dev/null and b/docs/handson-tl-py/img/6e1f5245-8381-41b4-8de3-f0ad95f59569.png differ diff --git a/docs/handson-tl-py/img/6f233bb4-f697-48f7-9028-da2846d34002.png b/docs/handson-tl-py/img/6f233bb4-f697-48f7-9028-da2846d34002.png new file mode 100644 index 0000000000000000000000000000000000000000..3ae73c539eeff9554ca0428094e4d30dce47c718 Binary files /dev/null and b/docs/handson-tl-py/img/6f233bb4-f697-48f7-9028-da2846d34002.png differ diff --git a/docs/handson-tl-py/img/70041e60-68d3-4ebf-900d-20bf3d0972b4.png b/docs/handson-tl-py/img/70041e60-68d3-4ebf-900d-20bf3d0972b4.png new file mode 100644 index 0000000000000000000000000000000000000000..564ce5a0cc5e102b5a5b2cf28a181001b687d961 Binary files /dev/null and b/docs/handson-tl-py/img/70041e60-68d3-4ebf-900d-20bf3d0972b4.png differ diff --git a/docs/handson-tl-py/img/7137e27c-ad6f-4d2b-a734-7add27c6a166.png b/docs/handson-tl-py/img/7137e27c-ad6f-4d2b-a734-7add27c6a166.png new file mode 100644 index 0000000000000000000000000000000000000000..032b73c8b1b3f055b691dea6a75d611bcccaeaaf Binary files /dev/null and b/docs/handson-tl-py/img/7137e27c-ad6f-4d2b-a734-7add27c6a166.png differ diff --git a/docs/handson-tl-py/img/713a7c6d-264b-4ba3-b7d9-6c85f31cf868.png b/docs/handson-tl-py/img/713a7c6d-264b-4ba3-b7d9-6c85f31cf868.png new file mode 100644 index 0000000000000000000000000000000000000000..ead541ac973472655d77d19925263215adc5053c Binary files /dev/null and b/docs/handson-tl-py/img/713a7c6d-264b-4ba3-b7d9-6c85f31cf868.png differ diff --git a/docs/handson-tl-py/img/721c6c66-6a07-4a20-a6c0-b8db4789461e.png b/docs/handson-tl-py/img/721c6c66-6a07-4a20-a6c0-b8db4789461e.png new file mode 100644 index 0000000000000000000000000000000000000000..7753e91fb0e0e40a01e5b3190bc219bb32d42131 Binary files /dev/null and b/docs/handson-tl-py/img/721c6c66-6a07-4a20-a6c0-b8db4789461e.png differ diff --git a/docs/handson-tl-py/img/72d4d8c2-aaaa-4e37-bca5-cc30ce55657f.png b/docs/handson-tl-py/img/72d4d8c2-aaaa-4e37-bca5-cc30ce55657f.png new file mode 100644 index 0000000000000000000000000000000000000000..ad32ae7cadc48da6e65cadf497c976a072f86db1 Binary files /dev/null and b/docs/handson-tl-py/img/72d4d8c2-aaaa-4e37-bca5-cc30ce55657f.png differ diff --git a/docs/handson-tl-py/img/7453a5d1-4826-47f8-bff3-93e57214e1cb.png b/docs/handson-tl-py/img/7453a5d1-4826-47f8-bff3-93e57214e1cb.png new file mode 100644 index 0000000000000000000000000000000000000000..445dc35a1345ef560bc898d12f72a62c9a4a157c Binary files /dev/null and b/docs/handson-tl-py/img/7453a5d1-4826-47f8-bff3-93e57214e1cb.png differ diff --git a/docs/handson-tl-py/img/74d14517-90ed-4828-aad4-1cb89b7b96e1.png b/docs/handson-tl-py/img/74d14517-90ed-4828-aad4-1cb89b7b96e1.png new file mode 100644 index 0000000000000000000000000000000000000000..934f645a269509831c46693a36c744e0720f8c43 Binary files /dev/null and b/docs/handson-tl-py/img/74d14517-90ed-4828-aad4-1cb89b7b96e1.png differ diff --git a/docs/handson-tl-py/img/75eb8b32-425b-4c71-aa65-fcf126587aca.png b/docs/handson-tl-py/img/75eb8b32-425b-4c71-aa65-fcf126587aca.png new file mode 100644 index 0000000000000000000000000000000000000000..42eb7167ab82afcd96191df8c9c4781c25abf4a5 Binary files /dev/null and b/docs/handson-tl-py/img/75eb8b32-425b-4c71-aa65-fcf126587aca.png differ diff --git a/docs/handson-tl-py/img/7628813a-e498-4e7a-9eaa-160d5169eebb.png b/docs/handson-tl-py/img/7628813a-e498-4e7a-9eaa-160d5169eebb.png new file mode 100644 index 0000000000000000000000000000000000000000..fc8e92c5c43022a09854ee1229ef8c16d6cde183 Binary files /dev/null and b/docs/handson-tl-py/img/7628813a-e498-4e7a-9eaa-160d5169eebb.png differ diff --git a/docs/handson-tl-py/img/76c3a773-50a7-4a3a-a94e-82ffc1fbc73f.png b/docs/handson-tl-py/img/76c3a773-50a7-4a3a-a94e-82ffc1fbc73f.png new file mode 100644 index 0000000000000000000000000000000000000000..ce2325a1651681f5179c6d55a7d948fbb510e015 Binary files /dev/null and b/docs/handson-tl-py/img/76c3a773-50a7-4a3a-a94e-82ffc1fbc73f.png differ diff --git a/docs/handson-tl-py/img/780a5531-7c6f-4872-b6fd-dba4f22ac05f.png b/docs/handson-tl-py/img/780a5531-7c6f-4872-b6fd-dba4f22ac05f.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d34079a87994649eddf94f899170fc527988ea Binary files /dev/null and b/docs/handson-tl-py/img/780a5531-7c6f-4872-b6fd-dba4f22ac05f.png differ diff --git a/docs/handson-tl-py/img/780f07f3-072e-4f03-8501-24a3786efbe4.png b/docs/handson-tl-py/img/780f07f3-072e-4f03-8501-24a3786efbe4.png new file mode 100644 index 0000000000000000000000000000000000000000..2aa750075186c6f985b75e7148fdd08422ba7067 Binary files /dev/null and b/docs/handson-tl-py/img/780f07f3-072e-4f03-8501-24a3786efbe4.png differ diff --git a/docs/handson-tl-py/img/78f9f35a-5341-47c2-8e0d-2759083c43a7.png b/docs/handson-tl-py/img/78f9f35a-5341-47c2-8e0d-2759083c43a7.png new file mode 100644 index 0000000000000000000000000000000000000000..930ef3830aa734171c9c4b0f039bff9249a435a4 Binary files /dev/null and b/docs/handson-tl-py/img/78f9f35a-5341-47c2-8e0d-2759083c43a7.png differ diff --git a/docs/handson-tl-py/img/794949a4-3e20-4ad0-8d38-060bc707b82a.png b/docs/handson-tl-py/img/794949a4-3e20-4ad0-8d38-060bc707b82a.png new file mode 100644 index 0000000000000000000000000000000000000000..ce4599bbd5a643653ed415b6675ef4873351e86d Binary files /dev/null and b/docs/handson-tl-py/img/794949a4-3e20-4ad0-8d38-060bc707b82a.png differ diff --git a/docs/handson-tl-py/img/79fc8d9c-a2fb-49d6-8510-922dafbd2317.png b/docs/handson-tl-py/img/79fc8d9c-a2fb-49d6-8510-922dafbd2317.png new file mode 100644 index 0000000000000000000000000000000000000000..ff5de37a0ce4b50c9c809624ee3f2e0b2f10cd4b Binary files /dev/null and b/docs/handson-tl-py/img/79fc8d9c-a2fb-49d6-8510-922dafbd2317.png differ diff --git a/docs/handson-tl-py/img/7bb09bf1-7394-43ba-b4a0-a17eee6abe1c.png b/docs/handson-tl-py/img/7bb09bf1-7394-43ba-b4a0-a17eee6abe1c.png new file mode 100644 index 0000000000000000000000000000000000000000..fe09e157e658034318bb461c2d066cd326fa442a Binary files /dev/null and b/docs/handson-tl-py/img/7bb09bf1-7394-43ba-b4a0-a17eee6abe1c.png differ diff --git a/docs/handson-tl-py/img/7c2d473b-3c3c-4d00-ab64-c2a35fe6dfc3.png b/docs/handson-tl-py/img/7c2d473b-3c3c-4d00-ab64-c2a35fe6dfc3.png new file mode 100644 index 0000000000000000000000000000000000000000..eab6128299c5b2c8b58aa2d9bc17854583bd99a1 Binary files /dev/null and b/docs/handson-tl-py/img/7c2d473b-3c3c-4d00-ab64-c2a35fe6dfc3.png differ diff --git a/docs/handson-tl-py/img/7da599f5-2250-4acf-b6ef-e8dd7d188957.png b/docs/handson-tl-py/img/7da599f5-2250-4acf-b6ef-e8dd7d188957.png new file mode 100644 index 0000000000000000000000000000000000000000..bd2114473fa44cf51ba3f448c16085429cb1a126 Binary files /dev/null and b/docs/handson-tl-py/img/7da599f5-2250-4acf-b6ef-e8dd7d188957.png differ diff --git a/docs/handson-tl-py/img/7e114bcc-d207-47f4-a6d0-7318698691f2.png b/docs/handson-tl-py/img/7e114bcc-d207-47f4-a6d0-7318698691f2.png new file mode 100644 index 0000000000000000000000000000000000000000..4e3c0536f6a3e681da7297b2ffd87fd781d0f3c9 Binary files /dev/null and b/docs/handson-tl-py/img/7e114bcc-d207-47f4-a6d0-7318698691f2.png differ diff --git a/docs/handson-tl-py/img/7e468aa3-1eb1-491a-8955-ee652dc45b57.png b/docs/handson-tl-py/img/7e468aa3-1eb1-491a-8955-ee652dc45b57.png new file mode 100644 index 0000000000000000000000000000000000000000..1aabc2585ea7f6ef3e3cdf68efa058a73c227fdb Binary files /dev/null and b/docs/handson-tl-py/img/7e468aa3-1eb1-491a-8955-ee652dc45b57.png differ diff --git a/docs/handson-tl-py/img/7edbfaa7-ef11-4918-b0bf-23ecb6bb5098.png b/docs/handson-tl-py/img/7edbfaa7-ef11-4918-b0bf-23ecb6bb5098.png new file mode 100644 index 0000000000000000000000000000000000000000..3fd4eefb339a2ae934064a2c2b4867bb0005ee20 Binary files /dev/null and b/docs/handson-tl-py/img/7edbfaa7-ef11-4918-b0bf-23ecb6bb5098.png differ diff --git a/docs/handson-tl-py/img/7ff92d2e-11bf-4c54-b366-f0f8d512d881.png b/docs/handson-tl-py/img/7ff92d2e-11bf-4c54-b366-f0f8d512d881.png new file mode 100644 index 0000000000000000000000000000000000000000..0fc4175ea78fbd2e5aeb43059dbb0f51b54f242f Binary files /dev/null and b/docs/handson-tl-py/img/7ff92d2e-11bf-4c54-b366-f0f8d512d881.png differ diff --git a/docs/handson-tl-py/img/80bb52c2-ee64-4f17-9739-0102fa0a8713.png b/docs/handson-tl-py/img/80bb52c2-ee64-4f17-9739-0102fa0a8713.png new file mode 100644 index 0000000000000000000000000000000000000000..ed4064c960bb00d82cc94633b1c3ee15d4bcc9ed Binary files /dev/null and b/docs/handson-tl-py/img/80bb52c2-ee64-4f17-9739-0102fa0a8713.png differ diff --git a/docs/handson-tl-py/img/80cad1ea-5177-4884-9617-442850bcbf2f.png b/docs/handson-tl-py/img/80cad1ea-5177-4884-9617-442850bcbf2f.png new file mode 100644 index 0000000000000000000000000000000000000000..fe8eba7d6e62f893e676ec37bf0df76ed85546e5 Binary files /dev/null and b/docs/handson-tl-py/img/80cad1ea-5177-4884-9617-442850bcbf2f.png differ diff --git a/docs/handson-tl-py/img/81c809b6-7fe6-40b3-aa4d-a3c84510adc0.png b/docs/handson-tl-py/img/81c809b6-7fe6-40b3-aa4d-a3c84510adc0.png new file mode 100644 index 0000000000000000000000000000000000000000..af69debce76d4b0b9c51e6e41c975ec46dc116eb Binary files /dev/null and b/docs/handson-tl-py/img/81c809b6-7fe6-40b3-aa4d-a3c84510adc0.png differ diff --git a/docs/handson-tl-py/img/823d1b46-4dad-4194-b79e-9cef6d5164fa.png b/docs/handson-tl-py/img/823d1b46-4dad-4194-b79e-9cef6d5164fa.png new file mode 100644 index 0000000000000000000000000000000000000000..40656f9ae466692e32b20618fedc4a6175541874 Binary files /dev/null and b/docs/handson-tl-py/img/823d1b46-4dad-4194-b79e-9cef6d5164fa.png differ diff --git a/docs/handson-tl-py/img/82968e26-b880-4ddb-9fca-76ed6228eb7b.png b/docs/handson-tl-py/img/82968e26-b880-4ddb-9fca-76ed6228eb7b.png new file mode 100644 index 0000000000000000000000000000000000000000..733a6586a6674b02239c0b431764e723fdd62efe Binary files /dev/null and b/docs/handson-tl-py/img/82968e26-b880-4ddb-9fca-76ed6228eb7b.png differ diff --git a/docs/handson-tl-py/img/8439d2a4-1315-4146-9eb0-59f0841cbaeb.png b/docs/handson-tl-py/img/8439d2a4-1315-4146-9eb0-59f0841cbaeb.png new file mode 100644 index 0000000000000000000000000000000000000000..44b4f9099126b1b1ecf9cdc93e9b9bcbae58d734 Binary files /dev/null and b/docs/handson-tl-py/img/8439d2a4-1315-4146-9eb0-59f0841cbaeb.png differ diff --git a/docs/handson-tl-py/img/843accda-1265-4103-96f8-ba73db66369a.png b/docs/handson-tl-py/img/843accda-1265-4103-96f8-ba73db66369a.png new file mode 100644 index 0000000000000000000000000000000000000000..83602b3e64ceea85f2f3931eec998e74979cd8a3 Binary files /dev/null and b/docs/handson-tl-py/img/843accda-1265-4103-96f8-ba73db66369a.png differ diff --git a/docs/handson-tl-py/img/849d376a-0a44-417d-9b75-9474549e2b03.png b/docs/handson-tl-py/img/849d376a-0a44-417d-9b75-9474549e2b03.png new file mode 100644 index 0000000000000000000000000000000000000000..3340ddea0f11041000d2485f983e92a1d6f642fe Binary files /dev/null and b/docs/handson-tl-py/img/849d376a-0a44-417d-9b75-9474549e2b03.png differ diff --git a/docs/handson-tl-py/img/84f83a45-f09c-4a0e-8da2-e760138193ea.png b/docs/handson-tl-py/img/84f83a45-f09c-4a0e-8da2-e760138193ea.png new file mode 100644 index 0000000000000000000000000000000000000000..535b9c9e328f56a6e7b084135413822606c13840 Binary files /dev/null and b/docs/handson-tl-py/img/84f83a45-f09c-4a0e-8da2-e760138193ea.png differ diff --git a/docs/handson-tl-py/img/8514cbc9-a36b-4fca-9a50-0ae02572bc74.png b/docs/handson-tl-py/img/8514cbc9-a36b-4fca-9a50-0ae02572bc74.png new file mode 100644 index 0000000000000000000000000000000000000000..21e7efe5e0b120bc475e83f9e64cb5ea48bfef17 Binary files /dev/null and b/docs/handson-tl-py/img/8514cbc9-a36b-4fca-9a50-0ae02572bc74.png differ diff --git a/docs/handson-tl-py/img/861ffce6-b1a1-4b20-a4e8-0d7aee34152b.png b/docs/handson-tl-py/img/861ffce6-b1a1-4b20-a4e8-0d7aee34152b.png new file mode 100644 index 0000000000000000000000000000000000000000..29bb7c8b115beeb4cf0d6b848cb75d63a35f7d92 Binary files /dev/null and b/docs/handson-tl-py/img/861ffce6-b1a1-4b20-a4e8-0d7aee34152b.png differ diff --git a/docs/handson-tl-py/img/86ce06a7-a636-494c-b62f-1addb7e0d1a3.png b/docs/handson-tl-py/img/86ce06a7-a636-494c-b62f-1addb7e0d1a3.png new file mode 100644 index 0000000000000000000000000000000000000000..a2d8e01c95b50a6554268ad525dfb71a39c3ecf7 Binary files /dev/null and b/docs/handson-tl-py/img/86ce06a7-a636-494c-b62f-1addb7e0d1a3.png differ diff --git a/docs/handson-tl-py/img/86e35b8e-9cc4-4655-8d61-5b3fefdd5d52.png b/docs/handson-tl-py/img/86e35b8e-9cc4-4655-8d61-5b3fefdd5d52.png new file mode 100644 index 0000000000000000000000000000000000000000..9211c2d7bd6eb033632e02976cd7a1a13fd63593 Binary files /dev/null and b/docs/handson-tl-py/img/86e35b8e-9cc4-4655-8d61-5b3fefdd5d52.png differ diff --git a/docs/handson-tl-py/img/87483eb5-1c35-4109-8e55-167fad61e513.png b/docs/handson-tl-py/img/87483eb5-1c35-4109-8e55-167fad61e513.png new file mode 100644 index 0000000000000000000000000000000000000000..ebba6cbec6c17fa470c4d5f8e4df354d1be35e4a Binary files /dev/null and b/docs/handson-tl-py/img/87483eb5-1c35-4109-8e55-167fad61e513.png differ diff --git a/docs/handson-tl-py/img/8761e861-000e-4a0a-a02a-c0c16476bb84.png b/docs/handson-tl-py/img/8761e861-000e-4a0a-a02a-c0c16476bb84.png new file mode 100644 index 0000000000000000000000000000000000000000..c11ea73eb0237f96870d129de8a5029b929bafa6 Binary files /dev/null and b/docs/handson-tl-py/img/8761e861-000e-4a0a-a02a-c0c16476bb84.png differ diff --git a/docs/handson-tl-py/img/88f6f0bb-b7d9-4a33-8d96-f46e8db8138b.png b/docs/handson-tl-py/img/88f6f0bb-b7d9-4a33-8d96-f46e8db8138b.png new file mode 100644 index 0000000000000000000000000000000000000000..b4dad9a1f08e50d963b70ee2395a02b43a836b6e Binary files /dev/null and b/docs/handson-tl-py/img/88f6f0bb-b7d9-4a33-8d96-f46e8db8138b.png differ diff --git a/docs/handson-tl-py/img/8919728b-7557-44b8-8fd1-b475ea3ac5c1.png b/docs/handson-tl-py/img/8919728b-7557-44b8-8fd1-b475ea3ac5c1.png new file mode 100644 index 0000000000000000000000000000000000000000..70871a42f1564dbd91ee6b41913752faa953a014 Binary files /dev/null and b/docs/handson-tl-py/img/8919728b-7557-44b8-8fd1-b475ea3ac5c1.png differ diff --git a/docs/handson-tl-py/img/8a932ea8-9a32-4475-8017-88d5e3fb917b.png b/docs/handson-tl-py/img/8a932ea8-9a32-4475-8017-88d5e3fb917b.png new file mode 100644 index 0000000000000000000000000000000000000000..12cafb0e0f8d23ee770fcb0e43baab88209b6abe Binary files /dev/null and b/docs/handson-tl-py/img/8a932ea8-9a32-4475-8017-88d5e3fb917b.png differ diff --git a/docs/handson-tl-py/img/8aafabe1-dc15-4785-a4ee-2d0abb44b08c.png b/docs/handson-tl-py/img/8aafabe1-dc15-4785-a4ee-2d0abb44b08c.png new file mode 100644 index 0000000000000000000000000000000000000000..b85d9371ecaa170f7b543ee418ae02d7b3e5d53a Binary files /dev/null and b/docs/handson-tl-py/img/8aafabe1-dc15-4785-a4ee-2d0abb44b08c.png differ diff --git a/docs/handson-tl-py/img/8b4c716c-2125-4744-b38f-367d4a598f62.png b/docs/handson-tl-py/img/8b4c716c-2125-4744-b38f-367d4a598f62.png new file mode 100644 index 0000000000000000000000000000000000000000..b0fcdf94c614d85a158f0463ea1270390ca8b9ca Binary files /dev/null and b/docs/handson-tl-py/img/8b4c716c-2125-4744-b38f-367d4a598f62.png differ diff --git a/docs/handson-tl-py/img/8c73dc5a-8829-42f1-8c3b-2f3e29daf28e.png b/docs/handson-tl-py/img/8c73dc5a-8829-42f1-8c3b-2f3e29daf28e.png new file mode 100644 index 0000000000000000000000000000000000000000..2ac5adb4132fb35ccc9489c8902462eca7eb809e Binary files /dev/null and b/docs/handson-tl-py/img/8c73dc5a-8829-42f1-8c3b-2f3e29daf28e.png differ diff --git a/docs/handson-tl-py/img/8caf892b-6c7d-44d5-a6d9-7203956b40b4.png b/docs/handson-tl-py/img/8caf892b-6c7d-44d5-a6d9-7203956b40b4.png new file mode 100644 index 0000000000000000000000000000000000000000..183c2dbb768fe5b2117d2af6548fe2d55412b1b8 Binary files /dev/null and b/docs/handson-tl-py/img/8caf892b-6c7d-44d5-a6d9-7203956b40b4.png differ diff --git a/docs/handson-tl-py/img/8ccc305e-368b-48e0-a031-c076776e0c1e.png b/docs/handson-tl-py/img/8ccc305e-368b-48e0-a031-c076776e0c1e.png new file mode 100644 index 0000000000000000000000000000000000000000..a0d528d9980b63c2b47851a2d33ff2a4372a2ff0 Binary files /dev/null and b/docs/handson-tl-py/img/8ccc305e-368b-48e0-a031-c076776e0c1e.png differ diff --git a/docs/handson-tl-py/img/8d0d1fbc-ce0d-4beb-93f9-259f72be5b1f.png b/docs/handson-tl-py/img/8d0d1fbc-ce0d-4beb-93f9-259f72be5b1f.png new file mode 100644 index 0000000000000000000000000000000000000000..08f5c31ddd93398b9ff1dc116aeca622292b959b Binary files /dev/null and b/docs/handson-tl-py/img/8d0d1fbc-ce0d-4beb-93f9-259f72be5b1f.png differ diff --git a/docs/handson-tl-py/img/8d79995a-b082-4768-9f41-24bc1876767d.png b/docs/handson-tl-py/img/8d79995a-b082-4768-9f41-24bc1876767d.png new file mode 100644 index 0000000000000000000000000000000000000000..046e6cfa6d95cf4d7804f50938a4562ea3fa22b2 Binary files /dev/null and b/docs/handson-tl-py/img/8d79995a-b082-4768-9f41-24bc1876767d.png differ diff --git a/docs/handson-tl-py/img/8f1cb58e-6396-4356-882e-e773f540c85e.png b/docs/handson-tl-py/img/8f1cb58e-6396-4356-882e-e773f540c85e.png new file mode 100644 index 0000000000000000000000000000000000000000..c7d19072a4a2cb2236314ad2efd2a0e5b401f16e Binary files /dev/null and b/docs/handson-tl-py/img/8f1cb58e-6396-4356-882e-e773f540c85e.png differ diff --git a/docs/handson-tl-py/img/8f47fa4c-3de7-4308-8f14-b2613169bdcc.png b/docs/handson-tl-py/img/8f47fa4c-3de7-4308-8f14-b2613169bdcc.png new file mode 100644 index 0000000000000000000000000000000000000000..be63861bff08e6bc14d1b4b989ae1795c10d7c14 Binary files /dev/null and b/docs/handson-tl-py/img/8f47fa4c-3de7-4308-8f14-b2613169bdcc.png differ diff --git a/docs/handson-tl-py/img/8f69a68a-52a7-4ae5-a2b1-019f7a344fc4.png b/docs/handson-tl-py/img/8f69a68a-52a7-4ae5-a2b1-019f7a344fc4.png new file mode 100644 index 0000000000000000000000000000000000000000..1b9d925d98c34e25cb12691723b36ca9c077b574 Binary files /dev/null and b/docs/handson-tl-py/img/8f69a68a-52a7-4ae5-a2b1-019f7a344fc4.png differ diff --git a/docs/handson-tl-py/img/90336338-d933-4938-99f5-5a8295c39919.png b/docs/handson-tl-py/img/90336338-d933-4938-99f5-5a8295c39919.png new file mode 100644 index 0000000000000000000000000000000000000000..4fefdda1eef2d354e957790912c9f2d17e809b7b Binary files /dev/null and b/docs/handson-tl-py/img/90336338-d933-4938-99f5-5a8295c39919.png differ diff --git a/docs/handson-tl-py/img/91149c74-17ef-432b-8b3a-d7449fc50523.png b/docs/handson-tl-py/img/91149c74-17ef-432b-8b3a-d7449fc50523.png new file mode 100644 index 0000000000000000000000000000000000000000..619068f2484dd796341d2941b899ae25edbd2953 Binary files /dev/null and b/docs/handson-tl-py/img/91149c74-17ef-432b-8b3a-d7449fc50523.png differ diff --git a/docs/handson-tl-py/img/93186224-8b4d-4459-98e7-a21fd5aaf615.png b/docs/handson-tl-py/img/93186224-8b4d-4459-98e7-a21fd5aaf615.png new file mode 100644 index 0000000000000000000000000000000000000000..fe09e093c01f1cc9143beded24e119ed7ff448e1 Binary files /dev/null and b/docs/handson-tl-py/img/93186224-8b4d-4459-98e7-a21fd5aaf615.png differ diff --git a/docs/handson-tl-py/img/93c63280-e0c9-49ad-b444-bd4c0edded2d.png b/docs/handson-tl-py/img/93c63280-e0c9-49ad-b444-bd4c0edded2d.png new file mode 100644 index 0000000000000000000000000000000000000000..eb2c09fb0e75d055d59af9cae062e03cc61043dc Binary files /dev/null and b/docs/handson-tl-py/img/93c63280-e0c9-49ad-b444-bd4c0edded2d.png differ diff --git a/docs/handson-tl-py/img/95f79b2d-3b5e-4219-bc97-62174d8ce7d2.png b/docs/handson-tl-py/img/95f79b2d-3b5e-4219-bc97-62174d8ce7d2.png new file mode 100644 index 0000000000000000000000000000000000000000..3dc8cb06b47212cfde396ebb56c0b5b5924b10b5 Binary files /dev/null and b/docs/handson-tl-py/img/95f79b2d-3b5e-4219-bc97-62174d8ce7d2.png differ diff --git a/docs/handson-tl-py/img/965ff81d-4930-4829-ab82-679911178735.png b/docs/handson-tl-py/img/965ff81d-4930-4829-ab82-679911178735.png new file mode 100644 index 0000000000000000000000000000000000000000..f46662921f21ef32645242b232432f8b70ccfe6f Binary files /dev/null and b/docs/handson-tl-py/img/965ff81d-4930-4829-ab82-679911178735.png differ diff --git a/docs/handson-tl-py/img/96c6867c-f6a9-4daa-bb4b-aa31e80cf6a9.png b/docs/handson-tl-py/img/96c6867c-f6a9-4daa-bb4b-aa31e80cf6a9.png new file mode 100644 index 0000000000000000000000000000000000000000..b9ee98b10ae1cdd4ad19a08b03cf7351db6c38bb Binary files /dev/null and b/docs/handson-tl-py/img/96c6867c-f6a9-4daa-bb4b-aa31e80cf6a9.png differ diff --git a/docs/handson-tl-py/img/98d383df-6e05-485e-b91d-4915cf14b77e.png b/docs/handson-tl-py/img/98d383df-6e05-485e-b91d-4915cf14b77e.png new file mode 100644 index 0000000000000000000000000000000000000000..3224cdb1763a47c3ca67b22920473825e9f09063 Binary files /dev/null and b/docs/handson-tl-py/img/98d383df-6e05-485e-b91d-4915cf14b77e.png differ diff --git a/docs/handson-tl-py/img/9973f689-d94a-49b1-97ec-90e66790785c.png b/docs/handson-tl-py/img/9973f689-d94a-49b1-97ec-90e66790785c.png new file mode 100644 index 0000000000000000000000000000000000000000..ebce18c5ef1b88f0d7f54fad94169f6215acaf0b Binary files /dev/null and b/docs/handson-tl-py/img/9973f689-d94a-49b1-97ec-90e66790785c.png differ diff --git a/docs/handson-tl-py/img/9b0a1cc5-3e19-4f90-9f9d-70dc2ecafe72.png b/docs/handson-tl-py/img/9b0a1cc5-3e19-4f90-9f9d-70dc2ecafe72.png new file mode 100644 index 0000000000000000000000000000000000000000..5e99a1ec3b25cffe18989fe18f616931f0490b1c Binary files /dev/null and b/docs/handson-tl-py/img/9b0a1cc5-3e19-4f90-9f9d-70dc2ecafe72.png differ diff --git a/docs/handson-tl-py/img/9b17fcb0-2380-4a14-a86a-bf3bf235f078.png b/docs/handson-tl-py/img/9b17fcb0-2380-4a14-a86a-bf3bf235f078.png new file mode 100644 index 0000000000000000000000000000000000000000..90087e633c5d304cf941f68a5e06de457c7417e7 Binary files /dev/null and b/docs/handson-tl-py/img/9b17fcb0-2380-4a14-a86a-bf3bf235f078.png differ diff --git a/docs/handson-tl-py/img/9b87a770-3ad0-498a-848e-7ab85919b52b.png b/docs/handson-tl-py/img/9b87a770-3ad0-498a-848e-7ab85919b52b.png new file mode 100644 index 0000000000000000000000000000000000000000..466df391358f16a40629e9cd1aaddc39a292edb1 Binary files /dev/null and b/docs/handson-tl-py/img/9b87a770-3ad0-498a-848e-7ab85919b52b.png differ diff --git a/docs/handson-tl-py/img/9c2898fa-79f4-4806-a527-d2a1fbf1f977.png b/docs/handson-tl-py/img/9c2898fa-79f4-4806-a527-d2a1fbf1f977.png new file mode 100644 index 0000000000000000000000000000000000000000..5ff8d09f2260cd95d54902cb7f1b9e74e3340abd Binary files /dev/null and b/docs/handson-tl-py/img/9c2898fa-79f4-4806-a527-d2a1fbf1f977.png differ diff --git a/docs/handson-tl-py/img/9c3d34f8-aa07-489d-b95e-1d215daf0dee.png b/docs/handson-tl-py/img/9c3d34f8-aa07-489d-b95e-1d215daf0dee.png new file mode 100644 index 0000000000000000000000000000000000000000..63a0b63125fa8aec61e90a899501e63703ee6336 Binary files /dev/null and b/docs/handson-tl-py/img/9c3d34f8-aa07-489d-b95e-1d215daf0dee.png differ diff --git a/docs/handson-tl-py/img/9c470dba-210a-4bfd-8a95-1227d45362b9.png b/docs/handson-tl-py/img/9c470dba-210a-4bfd-8a95-1227d45362b9.png new file mode 100644 index 0000000000000000000000000000000000000000..c337622e7d6f63c7a96f022fa4d61ceae27ea125 Binary files /dev/null and b/docs/handson-tl-py/img/9c470dba-210a-4bfd-8a95-1227d45362b9.png differ diff --git a/docs/handson-tl-py/img/9c4a31dd-9384-4d77-9229-5a45c60b24f9.png b/docs/handson-tl-py/img/9c4a31dd-9384-4d77-9229-5a45c60b24f9.png new file mode 100644 index 0000000000000000000000000000000000000000..1c20dcda8f3f74a5d3cb4c74d79028c507ba9734 Binary files /dev/null and b/docs/handson-tl-py/img/9c4a31dd-9384-4d77-9229-5a45c60b24f9.png differ diff --git a/docs/handson-tl-py/img/9c6d795e-28b3-449a-9a67-10405cbce1e1.png b/docs/handson-tl-py/img/9c6d795e-28b3-449a-9a67-10405cbce1e1.png new file mode 100644 index 0000000000000000000000000000000000000000..ac1af4706cfbeea0bc6a84608054dc7fba0ca9dc Binary files /dev/null and b/docs/handson-tl-py/img/9c6d795e-28b3-449a-9a67-10405cbce1e1.png differ diff --git a/docs/handson-tl-py/img/9cd9e2ed-8641-450d-90ec-204ea51f95bb.png b/docs/handson-tl-py/img/9cd9e2ed-8641-450d-90ec-204ea51f95bb.png new file mode 100644 index 0000000000000000000000000000000000000000..452c15f767549b10c4ce16bf657dc16eff74bb5b Binary files /dev/null and b/docs/handson-tl-py/img/9cd9e2ed-8641-450d-90ec-204ea51f95bb.png differ diff --git a/docs/handson-tl-py/img/9d284e1e-ba87-4fdf-ad26-6fa3fe11d2f0.png b/docs/handson-tl-py/img/9d284e1e-ba87-4fdf-ad26-6fa3fe11d2f0.png new file mode 100644 index 0000000000000000000000000000000000000000..445dc35a1345ef560bc898d12f72a62c9a4a157c Binary files /dev/null and b/docs/handson-tl-py/img/9d284e1e-ba87-4fdf-ad26-6fa3fe11d2f0.png differ diff --git a/docs/handson-tl-py/img/9da92d67-d3ef-48f5-9ea0-369abb747e48.png b/docs/handson-tl-py/img/9da92d67-d3ef-48f5-9ea0-369abb747e48.png new file mode 100644 index 0000000000000000000000000000000000000000..0e6ec8e815bcdeba0e198ab74eac28ca5a64c6d5 Binary files /dev/null and b/docs/handson-tl-py/img/9da92d67-d3ef-48f5-9ea0-369abb747e48.png differ diff --git a/docs/handson-tl-py/img/9ef786b3-25c9-4343-98c0-252fcf9d22f2.png b/docs/handson-tl-py/img/9ef786b3-25c9-4343-98c0-252fcf9d22f2.png new file mode 100644 index 0000000000000000000000000000000000000000..33c9d7cc1c4535d61ea23a8ad839f222b97c6830 Binary files /dev/null and b/docs/handson-tl-py/img/9ef786b3-25c9-4343-98c0-252fcf9d22f2.png differ diff --git a/docs/handson-tl-py/img/9f139cfc-8df5-44ca-b88e-db6314c11a3b.png b/docs/handson-tl-py/img/9f139cfc-8df5-44ca-b88e-db6314c11a3b.png new file mode 100644 index 0000000000000000000000000000000000000000..83b70ef19b082ede5b75228900f6bca6c9595c32 Binary files /dev/null and b/docs/handson-tl-py/img/9f139cfc-8df5-44ca-b88e-db6314c11a3b.png differ diff --git a/docs/handson-tl-py/img/9ff5a5d4-1ebb-4948-9c61-6e45b7a3a6d5.png b/docs/handson-tl-py/img/9ff5a5d4-1ebb-4948-9c61-6e45b7a3a6d5.png new file mode 100644 index 0000000000000000000000000000000000000000..7cbb240e9eae4f724fb1265f6d7ea2b24abc7ecb Binary files /dev/null and b/docs/handson-tl-py/img/9ff5a5d4-1ebb-4948-9c61-6e45b7a3a6d5.png differ diff --git a/docs/handson-tl-py/img/a00a62b9-5e97-490d-97ee-800fb15a456a.png b/docs/handson-tl-py/img/a00a62b9-5e97-490d-97ee-800fb15a456a.png new file mode 100644 index 0000000000000000000000000000000000000000..eb2c09fb0e75d055d59af9cae062e03cc61043dc Binary files /dev/null and b/docs/handson-tl-py/img/a00a62b9-5e97-490d-97ee-800fb15a456a.png differ diff --git a/docs/handson-tl-py/img/a12ccc7c-5aea-463f-bb1c-00077b0dcb85.png b/docs/handson-tl-py/img/a12ccc7c-5aea-463f-bb1c-00077b0dcb85.png new file mode 100644 index 0000000000000000000000000000000000000000..7689314976a144338d464e40dbbe43d241d01a49 Binary files /dev/null and b/docs/handson-tl-py/img/a12ccc7c-5aea-463f-bb1c-00077b0dcb85.png differ diff --git a/docs/handson-tl-py/img/a164d6a2-685a-4ad5-b1fe-3af618612e17.png b/docs/handson-tl-py/img/a164d6a2-685a-4ad5-b1fe-3af618612e17.png new file mode 100644 index 0000000000000000000000000000000000000000..e2cbb2c133a9c9329e93187177d2b0f4ac5693a0 Binary files /dev/null and b/docs/handson-tl-py/img/a164d6a2-685a-4ad5-b1fe-3af618612e17.png differ diff --git a/docs/handson-tl-py/img/a19a6e64-1663-4055-8823-101a8af77049.png b/docs/handson-tl-py/img/a19a6e64-1663-4055-8823-101a8af77049.png new file mode 100644 index 0000000000000000000000000000000000000000..ebf25e87804d987fef2f05ec1349e359140ff442 Binary files /dev/null and b/docs/handson-tl-py/img/a19a6e64-1663-4055-8823-101a8af77049.png differ diff --git a/docs/handson-tl-py/img/a1ec2d70-f56a-42f8-8ac8-08a88b943061.png b/docs/handson-tl-py/img/a1ec2d70-f56a-42f8-8ac8-08a88b943061.png new file mode 100644 index 0000000000000000000000000000000000000000..39515bd9981dd46c7b1185b290896ae2c24563a7 Binary files /dev/null and b/docs/handson-tl-py/img/a1ec2d70-f56a-42f8-8ac8-08a88b943061.png differ diff --git a/docs/handson-tl-py/img/a40c9f46-fc4a-4011-81c8-82581345bdbc.png b/docs/handson-tl-py/img/a40c9f46-fc4a-4011-81c8-82581345bdbc.png new file mode 100644 index 0000000000000000000000000000000000000000..eaba5a9420f4d535b94ff3ee2ee29ceb379f96fc Binary files /dev/null and b/docs/handson-tl-py/img/a40c9f46-fc4a-4011-81c8-82581345bdbc.png differ diff --git a/docs/handson-tl-py/img/a59194e9-3b84-4be6-b6d2-f1d2fa8c7f01.png b/docs/handson-tl-py/img/a59194e9-3b84-4be6-b6d2-f1d2fa8c7f01.png new file mode 100644 index 0000000000000000000000000000000000000000..a499cdfdd5b51b1650a81b08b2f9fbcd11e6cf2b Binary files /dev/null and b/docs/handson-tl-py/img/a59194e9-3b84-4be6-b6d2-f1d2fa8c7f01.png differ diff --git a/docs/handson-tl-py/img/a5eb7160-f340-4ee3-9f73-81489bf2a779.png b/docs/handson-tl-py/img/a5eb7160-f340-4ee3-9f73-81489bf2a779.png new file mode 100644 index 0000000000000000000000000000000000000000..110adbfec007ec4b5bd228caa812b73b6915ee4c Binary files /dev/null and b/docs/handson-tl-py/img/a5eb7160-f340-4ee3-9f73-81489bf2a779.png differ diff --git a/docs/handson-tl-py/img/a65a1d73-ae70-45c8-b643-0b27aa9fe394.png b/docs/handson-tl-py/img/a65a1d73-ae70-45c8-b643-0b27aa9fe394.png new file mode 100644 index 0000000000000000000000000000000000000000..893403608d4834c08c82385d9d35ae78480af000 Binary files /dev/null and b/docs/handson-tl-py/img/a65a1d73-ae70-45c8-b643-0b27aa9fe394.png differ diff --git a/docs/handson-tl-py/img/a6ac4f88-e171-4745-9939-4a40189f8c2f.png b/docs/handson-tl-py/img/a6ac4f88-e171-4745-9939-4a40189f8c2f.png new file mode 100644 index 0000000000000000000000000000000000000000..a1dae08fe833767d8d7832ce8001cc3c15eee744 Binary files /dev/null and b/docs/handson-tl-py/img/a6ac4f88-e171-4745-9939-4a40189f8c2f.png differ diff --git a/docs/handson-tl-py/img/a731f64c-9ff7-4540-a4b4-caad445dde92.png b/docs/handson-tl-py/img/a731f64c-9ff7-4540-a4b4-caad445dde92.png new file mode 100644 index 0000000000000000000000000000000000000000..ada78d4c524baaa651d451a2faec8d15f7b285d1 Binary files /dev/null and b/docs/handson-tl-py/img/a731f64c-9ff7-4540-a4b4-caad445dde92.png differ diff --git a/docs/handson-tl-py/img/a7a8c6fa-4111-49ce-bcd9-4c63458b427c.png b/docs/handson-tl-py/img/a7a8c6fa-4111-49ce-bcd9-4c63458b427c.png new file mode 100644 index 0000000000000000000000000000000000000000..5f011a1ae88d03c21f5c4a9c29cccff3b04041db Binary files /dev/null and b/docs/handson-tl-py/img/a7a8c6fa-4111-49ce-bcd9-4c63458b427c.png differ diff --git a/docs/handson-tl-py/img/aa9ae558-6358-4192-8d56-93d190e0865f.png b/docs/handson-tl-py/img/aa9ae558-6358-4192-8d56-93d190e0865f.png new file mode 100644 index 0000000000000000000000000000000000000000..11c25022c0ba16889cb95dec282c105a843fea76 Binary files /dev/null and b/docs/handson-tl-py/img/aa9ae558-6358-4192-8d56-93d190e0865f.png differ diff --git a/docs/handson-tl-py/img/ac5407ea-187e-44b0-9709-449e7be7bdcc.png b/docs/handson-tl-py/img/ac5407ea-187e-44b0-9709-449e7be7bdcc.png new file mode 100644 index 0000000000000000000000000000000000000000..615fb8866de2aedad826fcb4ad7c784edb5cf3de Binary files /dev/null and b/docs/handson-tl-py/img/ac5407ea-187e-44b0-9709-449e7be7bdcc.png differ diff --git a/docs/handson-tl-py/img/ae38af80-e7a5-45ff-a1b9-d7991de028b6.png b/docs/handson-tl-py/img/ae38af80-e7a5-45ff-a1b9-d7991de028b6.png new file mode 100644 index 0000000000000000000000000000000000000000..6f5a4b320c388f19eb1d7317fe9a4c1e9220c5ae Binary files /dev/null and b/docs/handson-tl-py/img/ae38af80-e7a5-45ff-a1b9-d7991de028b6.png differ diff --git a/docs/handson-tl-py/img/ae806812-bef5-4846-a7fd-698bdd380b0e.png b/docs/handson-tl-py/img/ae806812-bef5-4846-a7fd-698bdd380b0e.png new file mode 100644 index 0000000000000000000000000000000000000000..a030079900136fd264cdc66554a43d38f3841d65 Binary files /dev/null and b/docs/handson-tl-py/img/ae806812-bef5-4846-a7fd-698bdd380b0e.png differ diff --git a/docs/handson-tl-py/img/af2bf69a-947a-4c6e-a8f5-09769d6c213b.png b/docs/handson-tl-py/img/af2bf69a-947a-4c6e-a8f5-09769d6c213b.png new file mode 100644 index 0000000000000000000000000000000000000000..42301a5231075d97857b89f853a8630cbfd520d4 Binary files /dev/null and b/docs/handson-tl-py/img/af2bf69a-947a-4c6e-a8f5-09769d6c213b.png differ diff --git a/docs/handson-tl-py/img/b074fe7b-5c31-4dca-97b3-b7f37a8ee172.png b/docs/handson-tl-py/img/b074fe7b-5c31-4dca-97b3-b7f37a8ee172.png new file mode 100644 index 0000000000000000000000000000000000000000..8f8892c63e7da29f14ce61f77d61419774cb1ed6 Binary files /dev/null and b/docs/handson-tl-py/img/b074fe7b-5c31-4dca-97b3-b7f37a8ee172.png differ diff --git a/docs/handson-tl-py/img/b2a7d0c1-ab50-49bf-8b01-9b77ea936dda.png b/docs/handson-tl-py/img/b2a7d0c1-ab50-49bf-8b01-9b77ea936dda.png new file mode 100644 index 0000000000000000000000000000000000000000..9219195cc22e0223e3630b30ba5838bf5d253fe9 Binary files /dev/null and b/docs/handson-tl-py/img/b2a7d0c1-ab50-49bf-8b01-9b77ea936dda.png differ diff --git a/docs/handson-tl-py/img/b2f44b73-b96f-423b-ae01-abc016a16f8a.png b/docs/handson-tl-py/img/b2f44b73-b96f-423b-ae01-abc016a16f8a.png new file mode 100644 index 0000000000000000000000000000000000000000..ec564f9de7a7cf2375ce441b464ad4af98837e88 Binary files /dev/null and b/docs/handson-tl-py/img/b2f44b73-b96f-423b-ae01-abc016a16f8a.png differ diff --git a/docs/handson-tl-py/img/b302ed2d-cd9a-4870-9f71-fc6bc31b7871.png b/docs/handson-tl-py/img/b302ed2d-cd9a-4870-9f71-fc6bc31b7871.png new file mode 100644 index 0000000000000000000000000000000000000000..92fc5a8a3706a5992ab083ea2856fd2e6ace28d1 Binary files /dev/null and b/docs/handson-tl-py/img/b302ed2d-cd9a-4870-9f71-fc6bc31b7871.png differ diff --git a/docs/handson-tl-py/img/b3ad80ab-a9db-4728-bd61-8a91790e03b5.png b/docs/handson-tl-py/img/b3ad80ab-a9db-4728-bd61-8a91790e03b5.png new file mode 100644 index 0000000000000000000000000000000000000000..bf2a7cfd26d1f57cb2c01e90b07f4a0f03c643cb Binary files /dev/null and b/docs/handson-tl-py/img/b3ad80ab-a9db-4728-bd61-8a91790e03b5.png differ diff --git a/docs/handson-tl-py/img/b5e2f78f-af3a-4480-b5c1-745bfeea9898.png b/docs/handson-tl-py/img/b5e2f78f-af3a-4480-b5c1-745bfeea9898.png new file mode 100644 index 0000000000000000000000000000000000000000..8d6649f296e8e73a66c272b1077e6e0308dabcd5 Binary files /dev/null and b/docs/handson-tl-py/img/b5e2f78f-af3a-4480-b5c1-745bfeea9898.png differ diff --git a/docs/handson-tl-py/img/b64ba456-043e-4e33-8f2e-b9508efa63d8.png b/docs/handson-tl-py/img/b64ba456-043e-4e33-8f2e-b9508efa63d8.png new file mode 100644 index 0000000000000000000000000000000000000000..599589ad6802767c145828c1db3b646b7cabcbae Binary files /dev/null and b/docs/handson-tl-py/img/b64ba456-043e-4e33-8f2e-b9508efa63d8.png differ diff --git a/docs/handson-tl-py/img/b6ac6afe-c059-42a3-89cb-dbd4f5e81844.png b/docs/handson-tl-py/img/b6ac6afe-c059-42a3-89cb-dbd4f5e81844.png new file mode 100644 index 0000000000000000000000000000000000000000..21812e661de17e38b9c9aea8b9b4779e43067bb4 Binary files /dev/null and b/docs/handson-tl-py/img/b6ac6afe-c059-42a3-89cb-dbd4f5e81844.png differ diff --git a/docs/handson-tl-py/img/b7040153-d418-490d-bbde-52337e6c9c8d.png b/docs/handson-tl-py/img/b7040153-d418-490d-bbde-52337e6c9c8d.png new file mode 100644 index 0000000000000000000000000000000000000000..73aecf85dc9aaf2a6d4f5839c32f56eca6e0535c Binary files /dev/null and b/docs/handson-tl-py/img/b7040153-d418-490d-bbde-52337e6c9c8d.png differ diff --git a/docs/handson-tl-py/img/b74f5703-41a5-480e-8132-8f82b71f609c.png b/docs/handson-tl-py/img/b74f5703-41a5-480e-8132-8f82b71f609c.png new file mode 100644 index 0000000000000000000000000000000000000000..7a0057f384b6c40476a0fd8248a0be0701708e93 Binary files /dev/null and b/docs/handson-tl-py/img/b74f5703-41a5-480e-8132-8f82b71f609c.png differ diff --git a/docs/handson-tl-py/img/b78cd483-fea2-4c10-81ef-85f419250bd9.png b/docs/handson-tl-py/img/b78cd483-fea2-4c10-81ef-85f419250bd9.png new file mode 100644 index 0000000000000000000000000000000000000000..ee368f26e680f71db5c23f02053077fec9fca430 Binary files /dev/null and b/docs/handson-tl-py/img/b78cd483-fea2-4c10-81ef-85f419250bd9.png differ diff --git a/docs/handson-tl-py/img/bad934bc-2baf-4446-8bc6-e356e6bc6049.png b/docs/handson-tl-py/img/bad934bc-2baf-4446-8bc6-e356e6bc6049.png new file mode 100644 index 0000000000000000000000000000000000000000..9a3a02c48672a0a7f79cee2d3b1beba639a44d1d Binary files /dev/null and b/docs/handson-tl-py/img/bad934bc-2baf-4446-8bc6-e356e6bc6049.png differ diff --git a/docs/handson-tl-py/img/bc08ac4f-958f-4283-b679-35c221d7292a.png b/docs/handson-tl-py/img/bc08ac4f-958f-4283-b679-35c221d7292a.png new file mode 100644 index 0000000000000000000000000000000000000000..971f07a9eac7c6456b31f11d288e26812763a55d Binary files /dev/null and b/docs/handson-tl-py/img/bc08ac4f-958f-4283-b679-35c221d7292a.png differ diff --git a/docs/handson-tl-py/img/bc86d76b-a52c-4244-bcfb-fa61a5a35639.png b/docs/handson-tl-py/img/bc86d76b-a52c-4244-bcfb-fa61a5a35639.png new file mode 100644 index 0000000000000000000000000000000000000000..44580a0ab81c53307faaaa8cf4370bdeb5b42978 Binary files /dev/null and b/docs/handson-tl-py/img/bc86d76b-a52c-4244-bcfb-fa61a5a35639.png differ diff --git a/docs/handson-tl-py/img/bdfbe2ec-0f06-47ee-b805-057f3369231b.png b/docs/handson-tl-py/img/bdfbe2ec-0f06-47ee-b805-057f3369231b.png new file mode 100644 index 0000000000000000000000000000000000000000..676b4860dc089affa0606e06cd6f456243d609bc Binary files /dev/null and b/docs/handson-tl-py/img/bdfbe2ec-0f06-47ee-b805-057f3369231b.png differ diff --git a/docs/handson-tl-py/img/be3c2da1-6403-4145-8e3f-bc0c2b09bfb6.png b/docs/handson-tl-py/img/be3c2da1-6403-4145-8e3f-bc0c2b09bfb6.png new file mode 100644 index 0000000000000000000000000000000000000000..519ce0b3f2cdb6097eabd6d5ac7c9146a0e296a0 Binary files /dev/null and b/docs/handson-tl-py/img/be3c2da1-6403-4145-8e3f-bc0c2b09bfb6.png differ diff --git a/docs/handson-tl-py/img/bedd445a-346d-4dd4-97f8-bf38e2fe755c.png b/docs/handson-tl-py/img/bedd445a-346d-4dd4-97f8-bf38e2fe755c.png new file mode 100644 index 0000000000000000000000000000000000000000..1880f94961f69ba897df147edb97d37a9b54269b Binary files /dev/null and b/docs/handson-tl-py/img/bedd445a-346d-4dd4-97f8-bf38e2fe755c.png differ diff --git a/docs/handson-tl-py/img/bfa1f85c-82ab-4340-bbd0-8ca16ea2286a.png b/docs/handson-tl-py/img/bfa1f85c-82ab-4340-bbd0-8ca16ea2286a.png new file mode 100644 index 0000000000000000000000000000000000000000..5f1bd4996378357dfea4ec8fe947ef5db76f9d97 Binary files /dev/null and b/docs/handson-tl-py/img/bfa1f85c-82ab-4340-bbd0-8ca16ea2286a.png differ diff --git a/docs/handson-tl-py/img/c1d4835d-3647-4413-9da0-8e0b5ab7d2e0.png b/docs/handson-tl-py/img/c1d4835d-3647-4413-9da0-8e0b5ab7d2e0.png new file mode 100644 index 0000000000000000000000000000000000000000..34891234815add6839f1a686a104262db0127e52 Binary files /dev/null and b/docs/handson-tl-py/img/c1d4835d-3647-4413-9da0-8e0b5ab7d2e0.png differ diff --git a/docs/handson-tl-py/img/c2f733e7-5563-4092-aeec-2e286cba3f6d.png b/docs/handson-tl-py/img/c2f733e7-5563-4092-aeec-2e286cba3f6d.png new file mode 100644 index 0000000000000000000000000000000000000000..44833cc491b3d52464af2c03aa0325d64b1949f6 Binary files /dev/null and b/docs/handson-tl-py/img/c2f733e7-5563-4092-aeec-2e286cba3f6d.png differ diff --git a/docs/handson-tl-py/img/c309828b-41bc-450c-a72a-af65ae9e4d49.png b/docs/handson-tl-py/img/c309828b-41bc-450c-a72a-af65ae9e4d49.png new file mode 100644 index 0000000000000000000000000000000000000000..f0bb8ebe12b057de12a06b4797cedf8ad32b042d Binary files /dev/null and b/docs/handson-tl-py/img/c309828b-41bc-450c-a72a-af65ae9e4d49.png differ diff --git a/docs/handson-tl-py/img/c409633e-d70e-481a-b49c-da5e05090126.png b/docs/handson-tl-py/img/c409633e-d70e-481a-b49c-da5e05090126.png new file mode 100644 index 0000000000000000000000000000000000000000..8925b4295af504fe81c4c223eedf7188cd89f595 Binary files /dev/null and b/docs/handson-tl-py/img/c409633e-d70e-481a-b49c-da5e05090126.png differ diff --git a/docs/handson-tl-py/img/c43e6248-8076-41c3-b8cb-92a4470a4e47.png b/docs/handson-tl-py/img/c43e6248-8076-41c3-b8cb-92a4470a4e47.png new file mode 100644 index 0000000000000000000000000000000000000000..94c1e61ced8e9a0f0ab58958b42ca1890c403b03 Binary files /dev/null and b/docs/handson-tl-py/img/c43e6248-8076-41c3-b8cb-92a4470a4e47.png differ diff --git a/docs/handson-tl-py/img/c591f7f1-05aa-4bda-8f15-b98ea25a8336.png b/docs/handson-tl-py/img/c591f7f1-05aa-4bda-8f15-b98ea25a8336.png new file mode 100644 index 0000000000000000000000000000000000000000..3ff36941f7911739207e6a48eaf5200f9adf6faf Binary files /dev/null and b/docs/handson-tl-py/img/c591f7f1-05aa-4bda-8f15-b98ea25a8336.png differ diff --git a/docs/handson-tl-py/img/c65cedd9-a157-4389-8433-18945df2fc9d.png b/docs/handson-tl-py/img/c65cedd9-a157-4389-8433-18945df2fc9d.png new file mode 100644 index 0000000000000000000000000000000000000000..233ba0476ea3f9112f796544c91da25a2051ad4c Binary files /dev/null and b/docs/handson-tl-py/img/c65cedd9-a157-4389-8433-18945df2fc9d.png differ diff --git a/docs/handson-tl-py/img/c6d124bb-1721-4458-82e8-4d805f6440b6.png b/docs/handson-tl-py/img/c6d124bb-1721-4458-82e8-4d805f6440b6.png new file mode 100644 index 0000000000000000000000000000000000000000..a2860844306619d915d4bf5993f97f58a84b49a4 Binary files /dev/null and b/docs/handson-tl-py/img/c6d124bb-1721-4458-82e8-4d805f6440b6.png differ diff --git a/docs/handson-tl-py/img/c7b1a382-402e-4cd3-8c9a-783da142e863.png b/docs/handson-tl-py/img/c7b1a382-402e-4cd3-8c9a-783da142e863.png new file mode 100644 index 0000000000000000000000000000000000000000..7ab7b82364b480815dae91859be3449a450e907e Binary files /dev/null and b/docs/handson-tl-py/img/c7b1a382-402e-4cd3-8c9a-783da142e863.png differ diff --git a/docs/handson-tl-py/img/c7eb65b7-41d2-4873-943e-07764f579812.png b/docs/handson-tl-py/img/c7eb65b7-41d2-4873-943e-07764f579812.png new file mode 100644 index 0000000000000000000000000000000000000000..d0989e8504e6d1b0b5495c079e3744bd8864667e Binary files /dev/null and b/docs/handson-tl-py/img/c7eb65b7-41d2-4873-943e-07764f579812.png differ diff --git a/docs/handson-tl-py/img/c801b87d-0f66-4b0c-beb4-9f16a7998be1.png b/docs/handson-tl-py/img/c801b87d-0f66-4b0c-beb4-9f16a7998be1.png new file mode 100644 index 0000000000000000000000000000000000000000..be3f9700a2b4d35165ebd1250dbc9157354eb6c5 Binary files /dev/null and b/docs/handson-tl-py/img/c801b87d-0f66-4b0c-beb4-9f16a7998be1.png differ diff --git a/docs/handson-tl-py/img/cb28331b-c874-4ffc-bb59-a54e9a855452.jpg b/docs/handson-tl-py/img/cb28331b-c874-4ffc-bb59-a54e9a855452.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f1437a1e5cb17616bacb38c0faf01fcfcfb071bc Binary files /dev/null and b/docs/handson-tl-py/img/cb28331b-c874-4ffc-bb59-a54e9a855452.jpg differ diff --git a/docs/handson-tl-py/img/cec84552-8b64-4c85-a3c1-41ebd47bfb10.png b/docs/handson-tl-py/img/cec84552-8b64-4c85-a3c1-41ebd47bfb10.png new file mode 100644 index 0000000000000000000000000000000000000000..3b0652a45b8bee41aab39ef9f8b088a442027982 Binary files /dev/null and b/docs/handson-tl-py/img/cec84552-8b64-4c85-a3c1-41ebd47bfb10.png differ diff --git a/docs/handson-tl-py/img/cf82886a-ab82-4903-a258-0b84f3f27b68.png b/docs/handson-tl-py/img/cf82886a-ab82-4903-a258-0b84f3f27b68.png new file mode 100644 index 0000000000000000000000000000000000000000..1e8b5f0a2c9594b7764707ec0c5be2d0bde9c0fc Binary files /dev/null and b/docs/handson-tl-py/img/cf82886a-ab82-4903-a258-0b84f3f27b68.png differ diff --git a/docs/handson-tl-py/img/d091a430-a904-4c49-9b84-cc431209bfca.png b/docs/handson-tl-py/img/d091a430-a904-4c49-9b84-cc431209bfca.png new file mode 100644 index 0000000000000000000000000000000000000000..a8f5298d734f7eeb5d2842df1f5c7e6f1365be35 Binary files /dev/null and b/docs/handson-tl-py/img/d091a430-a904-4c49-9b84-cc431209bfca.png differ diff --git a/docs/handson-tl-py/img/d120bc80-6174-4e45-91a0-7f900fb3142f.png b/docs/handson-tl-py/img/d120bc80-6174-4e45-91a0-7f900fb3142f.png new file mode 100644 index 0000000000000000000000000000000000000000..7026ac841aaec251175d0e8609f2bb543d6a86c9 Binary files /dev/null and b/docs/handson-tl-py/img/d120bc80-6174-4e45-91a0-7f900fb3142f.png differ diff --git a/docs/handson-tl-py/img/d1691af1-2b86-419c-971e-13d4fa14d885.png b/docs/handson-tl-py/img/d1691af1-2b86-419c-971e-13d4fa14d885.png new file mode 100644 index 0000000000000000000000000000000000000000..4f29e46d2a670bb99800dc07649463abc94b0a89 Binary files /dev/null and b/docs/handson-tl-py/img/d1691af1-2b86-419c-971e-13d4fa14d885.png differ diff --git a/docs/handson-tl-py/img/d16b854f-b4e3-447f-8ff4-6702a6e5743e.png b/docs/handson-tl-py/img/d16b854f-b4e3-447f-8ff4-6702a6e5743e.png new file mode 100644 index 0000000000000000000000000000000000000000..2ac5adb4132fb35ccc9489c8902462eca7eb809e Binary files /dev/null and b/docs/handson-tl-py/img/d16b854f-b4e3-447f-8ff4-6702a6e5743e.png differ diff --git a/docs/handson-tl-py/img/d1c377c4-f931-40de-9351-bd1a7960efcb.png b/docs/handson-tl-py/img/d1c377c4-f931-40de-9351-bd1a7960efcb.png new file mode 100644 index 0000000000000000000000000000000000000000..24110f055a5be122331680b73515647cbe3f48b7 Binary files /dev/null and b/docs/handson-tl-py/img/d1c377c4-f931-40de-9351-bd1a7960efcb.png differ diff --git a/docs/handson-tl-py/img/d21e706b-b7fc-4321-8536-06c2f01ee8ce.png b/docs/handson-tl-py/img/d21e706b-b7fc-4321-8536-06c2f01ee8ce.png new file mode 100644 index 0000000000000000000000000000000000000000..23cb3d6c936eb5ba14dcc86473ce58ca57cca334 Binary files /dev/null and b/docs/handson-tl-py/img/d21e706b-b7fc-4321-8536-06c2f01ee8ce.png differ diff --git a/docs/handson-tl-py/img/d2a960c4-8726-4451-b37b-87c7453cc2d0.png b/docs/handson-tl-py/img/d2a960c4-8726-4451-b37b-87c7453cc2d0.png new file mode 100644 index 0000000000000000000000000000000000000000..ef2e8daa9630b4b9b604bdfb77c1eba2a58859a5 Binary files /dev/null and b/docs/handson-tl-py/img/d2a960c4-8726-4451-b37b-87c7453cc2d0.png differ diff --git a/docs/handson-tl-py/img/d2be8a4c-1bd0-415e-ae4f-39d41a0985df.png b/docs/handson-tl-py/img/d2be8a4c-1bd0-415e-ae4f-39d41a0985df.png new file mode 100644 index 0000000000000000000000000000000000000000..e07e5ea2d0f04c3e763b5c8f8d9f900252987ab1 Binary files /dev/null and b/docs/handson-tl-py/img/d2be8a4c-1bd0-415e-ae4f-39d41a0985df.png differ diff --git a/docs/handson-tl-py/img/d35c4569-5d5f-4fd3-8433-9e3b2ee1790f.png b/docs/handson-tl-py/img/d35c4569-5d5f-4fd3-8433-9e3b2ee1790f.png new file mode 100644 index 0000000000000000000000000000000000000000..0aefd5944c89c02d5a78315574b24eb521191aac Binary files /dev/null and b/docs/handson-tl-py/img/d35c4569-5d5f-4fd3-8433-9e3b2ee1790f.png differ diff --git a/docs/handson-tl-py/img/d38f922d-b1ed-41c4-888c-146a5fa4c0a1.png b/docs/handson-tl-py/img/d38f922d-b1ed-41c4-888c-146a5fa4c0a1.png new file mode 100644 index 0000000000000000000000000000000000000000..bcf03103e6353ab86f2515eb3f919faf8c47e5a9 Binary files /dev/null and b/docs/handson-tl-py/img/d38f922d-b1ed-41c4-888c-146a5fa4c0a1.png differ diff --git a/docs/handson-tl-py/img/d482809f-0e10-45e6-8321-9859a4c068ce.png b/docs/handson-tl-py/img/d482809f-0e10-45e6-8321-9859a4c068ce.png new file mode 100644 index 0000000000000000000000000000000000000000..d76f289631ffe440a620a80dae7a8289615b0548 Binary files /dev/null and b/docs/handson-tl-py/img/d482809f-0e10-45e6-8321-9859a4c068ce.png differ diff --git a/docs/handson-tl-py/img/d521aea6-1fec-43fc-aca4-3b2550d09ca2.png b/docs/handson-tl-py/img/d521aea6-1fec-43fc-aca4-3b2550d09ca2.png new file mode 100644 index 0000000000000000000000000000000000000000..b8133732da9f6affeb9cd46bb8c2a9ae02a90f8a Binary files /dev/null and b/docs/handson-tl-py/img/d521aea6-1fec-43fc-aca4-3b2550d09ca2.png differ diff --git a/docs/handson-tl-py/img/d6252a46-1170-4bd3-a61c-ec067acd5310.png b/docs/handson-tl-py/img/d6252a46-1170-4bd3-a61c-ec067acd5310.png new file mode 100644 index 0000000000000000000000000000000000000000..354868863a95aa65535792bec64cebb06dce10d4 Binary files /dev/null and b/docs/handson-tl-py/img/d6252a46-1170-4bd3-a61c-ec067acd5310.png differ diff --git a/docs/handson-tl-py/img/d632f776-113d-4a9f-b1ae-f66c03800e11.png b/docs/handson-tl-py/img/d632f776-113d-4a9f-b1ae-f66c03800e11.png new file mode 100644 index 0000000000000000000000000000000000000000..6905b97e4176641c7ecf1d5001548d6a04fd4f96 Binary files /dev/null and b/docs/handson-tl-py/img/d632f776-113d-4a9f-b1ae-f66c03800e11.png differ diff --git a/docs/handson-tl-py/img/d6ae84fd-03b3-4511-9ce7-cf04ed45a6c9.png b/docs/handson-tl-py/img/d6ae84fd-03b3-4511-9ce7-cf04ed45a6c9.png new file mode 100644 index 0000000000000000000000000000000000000000..fcdd0bf40a0faeb5e42ebe1a3384a702740dee63 Binary files /dev/null and b/docs/handson-tl-py/img/d6ae84fd-03b3-4511-9ce7-cf04ed45a6c9.png differ diff --git a/docs/handson-tl-py/img/d6e194c3-df63-489c-94bd-527a19d1783f.png b/docs/handson-tl-py/img/d6e194c3-df63-489c-94bd-527a19d1783f.png new file mode 100644 index 0000000000000000000000000000000000000000..90be435a24074f9cfe8d863a71b0e2df291bb5d0 Binary files /dev/null and b/docs/handson-tl-py/img/d6e194c3-df63-489c-94bd-527a19d1783f.png differ diff --git a/docs/handson-tl-py/img/d71299bb-48ef-4a93-9098-e0aa50229f51.png b/docs/handson-tl-py/img/d71299bb-48ef-4a93-9098-e0aa50229f51.png new file mode 100644 index 0000000000000000000000000000000000000000..97ab06aafdcd8c68039db5e434ca0c506980296b Binary files /dev/null and b/docs/handson-tl-py/img/d71299bb-48ef-4a93-9098-e0aa50229f51.png differ diff --git a/docs/handson-tl-py/img/d7ae1192-1196-4254-93cc-028dd18d4d65.png b/docs/handson-tl-py/img/d7ae1192-1196-4254-93cc-028dd18d4d65.png new file mode 100644 index 0000000000000000000000000000000000000000..142ba6fea139f2a7b1ac3c795d57544e9d07aa23 Binary files /dev/null and b/docs/handson-tl-py/img/d7ae1192-1196-4254-93cc-028dd18d4d65.png differ diff --git a/docs/handson-tl-py/img/d7ee5e5b-72b4-4115-a7e2-f5f0834b7053.png b/docs/handson-tl-py/img/d7ee5e5b-72b4-4115-a7e2-f5f0834b7053.png new file mode 100644 index 0000000000000000000000000000000000000000..9c9e4ee822d2f8c3f678ce82da620e253408e40a Binary files /dev/null and b/docs/handson-tl-py/img/d7ee5e5b-72b4-4115-a7e2-f5f0834b7053.png differ diff --git a/docs/handson-tl-py/img/d8116e36-e0dc-469c-8872-ca06a7d83261.png b/docs/handson-tl-py/img/d8116e36-e0dc-469c-8872-ca06a7d83261.png new file mode 100644 index 0000000000000000000000000000000000000000..1db6a98b2e8e4374f19b1a7b2ff3de1d9c11f028 Binary files /dev/null and b/docs/handson-tl-py/img/d8116e36-e0dc-469c-8872-ca06a7d83261.png differ diff --git a/docs/handson-tl-py/img/d90cb654-5761-44ca-b03a-e253fed0e9b2.png b/docs/handson-tl-py/img/d90cb654-5761-44ca-b03a-e253fed0e9b2.png new file mode 100644 index 0000000000000000000000000000000000000000..7c80fcb180380b8a3c9cf6a48e5e7dfa4e7ce755 Binary files /dev/null and b/docs/handson-tl-py/img/d90cb654-5761-44ca-b03a-e253fed0e9b2.png differ diff --git a/docs/handson-tl-py/img/db41fbac-3130-4690-af32-deff0262e31d.png b/docs/handson-tl-py/img/db41fbac-3130-4690-af32-deff0262e31d.png new file mode 100644 index 0000000000000000000000000000000000000000..78114a013bc18f5d45b05dc5dd454f35ec5c5c4f Binary files /dev/null and b/docs/handson-tl-py/img/db41fbac-3130-4690-af32-deff0262e31d.png differ diff --git a/docs/handson-tl-py/img/db437583-5d9f-45a6-8743-2f82b51f79ac.png b/docs/handson-tl-py/img/db437583-5d9f-45a6-8743-2f82b51f79ac.png new file mode 100644 index 0000000000000000000000000000000000000000..866d9a8c724d372015c2e34ba1ec8f72cdf9bcab Binary files /dev/null and b/docs/handson-tl-py/img/db437583-5d9f-45a6-8743-2f82b51f79ac.png differ diff --git a/docs/handson-tl-py/img/dc2ec3d2-6f44-4905-85bf-cf64dc2599ed.png b/docs/handson-tl-py/img/dc2ec3d2-6f44-4905-85bf-cf64dc2599ed.png new file mode 100644 index 0000000000000000000000000000000000000000..0a9caf4302a4d7d017ead11a83ad6e9e21bd4295 Binary files /dev/null and b/docs/handson-tl-py/img/dc2ec3d2-6f44-4905-85bf-cf64dc2599ed.png differ diff --git a/docs/handson-tl-py/img/dc4229cc-0d2e-44a0-99a8-b6792459c1d2.png b/docs/handson-tl-py/img/dc4229cc-0d2e-44a0-99a8-b6792459c1d2.png new file mode 100644 index 0000000000000000000000000000000000000000..e57b48012eb6fdc3e1f1c5fec32d106e6ae6a905 Binary files /dev/null and b/docs/handson-tl-py/img/dc4229cc-0d2e-44a0-99a8-b6792459c1d2.png differ diff --git a/docs/handson-tl-py/img/dc8b0dfb-4929-42ec-ab41-196bf28b0bbe.png b/docs/handson-tl-py/img/dc8b0dfb-4929-42ec-ab41-196bf28b0bbe.png new file mode 100644 index 0000000000000000000000000000000000000000..cae4dcd48d8d8b9b50b23f6c409b7cbb0fe269a1 Binary files /dev/null and b/docs/handson-tl-py/img/dc8b0dfb-4929-42ec-ab41-196bf28b0bbe.png differ diff --git a/docs/handson-tl-py/img/dd8a372e-8efa-4671-bb94-4831617ad446.png b/docs/handson-tl-py/img/dd8a372e-8efa-4671-bb94-4831617ad446.png new file mode 100644 index 0000000000000000000000000000000000000000..580c38d614b3a381eb0e100c589857e2088c26fd Binary files /dev/null and b/docs/handson-tl-py/img/dd8a372e-8efa-4671-bb94-4831617ad446.png differ diff --git a/docs/handson-tl-py/img/de9875f7-8674-4f2f-909a-828daa8bd1c0.png b/docs/handson-tl-py/img/de9875f7-8674-4f2f-909a-828daa8bd1c0.png new file mode 100644 index 0000000000000000000000000000000000000000..1a84ea16ce3d549cdb02e89b128258509b80fed5 Binary files /dev/null and b/docs/handson-tl-py/img/de9875f7-8674-4f2f-909a-828daa8bd1c0.png differ diff --git a/docs/handson-tl-py/img/df6293d5-9713-4029-87ea-4649b69c8da4.png b/docs/handson-tl-py/img/df6293d5-9713-4029-87ea-4649b69c8da4.png new file mode 100644 index 0000000000000000000000000000000000000000..c7f8b0991f036fffc8e2ad593191841f5a9882d4 Binary files /dev/null and b/docs/handson-tl-py/img/df6293d5-9713-4029-87ea-4649b69c8da4.png differ diff --git a/docs/handson-tl-py/img/e1a9d83e-820d-491b-8427-d1068af52a1e.png b/docs/handson-tl-py/img/e1a9d83e-820d-491b-8427-d1068af52a1e.png new file mode 100644 index 0000000000000000000000000000000000000000..56f2bcc64e7dfd9143cceea912263b2fb66e665c Binary files /dev/null and b/docs/handson-tl-py/img/e1a9d83e-820d-491b-8427-d1068af52a1e.png differ diff --git a/docs/handson-tl-py/img/e5207a55-3734-47f0-8c63-9ce3427c1841.png b/docs/handson-tl-py/img/e5207a55-3734-47f0-8c63-9ce3427c1841.png new file mode 100644 index 0000000000000000000000000000000000000000..c9c70d6007bc1daef4c561f9f9bea999668614c2 Binary files /dev/null and b/docs/handson-tl-py/img/e5207a55-3734-47f0-8c63-9ce3427c1841.png differ diff --git a/docs/handson-tl-py/img/e567faa7-d096-4c23-a921-00b647dced4f.png b/docs/handson-tl-py/img/e567faa7-d096-4c23-a921-00b647dced4f.png new file mode 100644 index 0000000000000000000000000000000000000000..831e445fff837c188a9b34a93a540efd989d3689 Binary files /dev/null and b/docs/handson-tl-py/img/e567faa7-d096-4c23-a921-00b647dced4f.png differ diff --git a/docs/handson-tl-py/img/e57028ee-b73a-4e32-b034-4c7dfb253989.png b/docs/handson-tl-py/img/e57028ee-b73a-4e32-b034-4c7dfb253989.png new file mode 100644 index 0000000000000000000000000000000000000000..a719bed49764066c396d0f6d71b1589ad9e4bb0e Binary files /dev/null and b/docs/handson-tl-py/img/e57028ee-b73a-4e32-b034-4c7dfb253989.png differ diff --git a/docs/handson-tl-py/img/e6a2071b-06ad-4962-91d1-2a66bf08c6e0.png b/docs/handson-tl-py/img/e6a2071b-06ad-4962-91d1-2a66bf08c6e0.png new file mode 100644 index 0000000000000000000000000000000000000000..7f2101823b94ad4c808d315e1814b382bc036d72 Binary files /dev/null and b/docs/handson-tl-py/img/e6a2071b-06ad-4962-91d1-2a66bf08c6e0.png differ diff --git a/docs/handson-tl-py/img/e6b49a78-6a3d-4f24-9cb0-ab2b86e2591b.png b/docs/handson-tl-py/img/e6b49a78-6a3d-4f24-9cb0-ab2b86e2591b.png new file mode 100644 index 0000000000000000000000000000000000000000..015025996bc3811aeda2264d470077970a7534a2 Binary files /dev/null and b/docs/handson-tl-py/img/e6b49a78-6a3d-4f24-9cb0-ab2b86e2591b.png differ diff --git a/docs/handson-tl-py/img/e6d21542-8a77-4a59-8dbe-58e16803c0be.png b/docs/handson-tl-py/img/e6d21542-8a77-4a59-8dbe-58e16803c0be.png new file mode 100644 index 0000000000000000000000000000000000000000..847d2c3b463189339dd71ee36ea6caef92e14f1d Binary files /dev/null and b/docs/handson-tl-py/img/e6d21542-8a77-4a59-8dbe-58e16803c0be.png differ diff --git a/docs/handson-tl-py/img/e8526d3b-0776-40c7-8c03-aa7933a3889c.png b/docs/handson-tl-py/img/e8526d3b-0776-40c7-8c03-aa7933a3889c.png new file mode 100644 index 0000000000000000000000000000000000000000..23803e722372a7edf05c0355479778c77bf6a0d8 Binary files /dev/null and b/docs/handson-tl-py/img/e8526d3b-0776-40c7-8c03-aa7933a3889c.png differ diff --git a/docs/handson-tl-py/img/ea7fa58f-ba10-4924-a55c-5875ee0b307b.png b/docs/handson-tl-py/img/ea7fa58f-ba10-4924-a55c-5875ee0b307b.png new file mode 100644 index 0000000000000000000000000000000000000000..e8854e277e824be390b97c148cae0046d8e3529e Binary files /dev/null and b/docs/handson-tl-py/img/ea7fa58f-ba10-4924-a55c-5875ee0b307b.png differ diff --git a/docs/handson-tl-py/img/ec57fb0d-1df6-4532-af60-06400ed77fd5.png b/docs/handson-tl-py/img/ec57fb0d-1df6-4532-af60-06400ed77fd5.png new file mode 100644 index 0000000000000000000000000000000000000000..a6520d7bacc59226a5e0429d27e74ae053df6f51 Binary files /dev/null and b/docs/handson-tl-py/img/ec57fb0d-1df6-4532-af60-06400ed77fd5.png differ diff --git a/docs/handson-tl-py/img/eca8cda7-e58c-4af2-ad66-5d3f9208be75.png b/docs/handson-tl-py/img/eca8cda7-e58c-4af2-ad66-5d3f9208be75.png new file mode 100644 index 0000000000000000000000000000000000000000..00f21de3b1f836a9b629617a87cd8f6af5dd0ee2 Binary files /dev/null and b/docs/handson-tl-py/img/eca8cda7-e58c-4af2-ad66-5d3f9208be75.png differ diff --git a/docs/handson-tl-py/img/ece5b21e-0d53-4e50-9b34-5249ab3debe7.png b/docs/handson-tl-py/img/ece5b21e-0d53-4e50-9b34-5249ab3debe7.png new file mode 100644 index 0000000000000000000000000000000000000000..7ef519a46d4b89fa75802d472af62fc50e70a760 Binary files /dev/null and b/docs/handson-tl-py/img/ece5b21e-0d53-4e50-9b34-5249ab3debe7.png differ diff --git a/docs/handson-tl-py/img/ed31ddc9-a4de-4ebe-8456-b06f50d3e897.png b/docs/handson-tl-py/img/ed31ddc9-a4de-4ebe-8456-b06f50d3e897.png new file mode 100644 index 0000000000000000000000000000000000000000..da48158ff94d8e4a8771787bb2ae3a1c305f1b8e Binary files /dev/null and b/docs/handson-tl-py/img/ed31ddc9-a4de-4ebe-8456-b06f50d3e897.png differ diff --git a/docs/handson-tl-py/img/eda57a9e-cf19-4646-bd30-9138f3371662.png b/docs/handson-tl-py/img/eda57a9e-cf19-4646-bd30-9138f3371662.png new file mode 100644 index 0000000000000000000000000000000000000000..27fffe4bdf97ce246ac43ba11ce6754ff027db4b Binary files /dev/null and b/docs/handson-tl-py/img/eda57a9e-cf19-4646-bd30-9138f3371662.png differ diff --git a/docs/handson-tl-py/img/ee23ffeb-3522-48a2-be49-14afc13b75b4.png b/docs/handson-tl-py/img/ee23ffeb-3522-48a2-be49-14afc13b75b4.png new file mode 100644 index 0000000000000000000000000000000000000000..ef84138934c842c57e6693a0f9e0dc05c243892a Binary files /dev/null and b/docs/handson-tl-py/img/ee23ffeb-3522-48a2-be49-14afc13b75b4.png differ diff --git a/docs/handson-tl-py/img/ee49aebe-d4f7-4d07-b7bb-d7df651b096d.png b/docs/handson-tl-py/img/ee49aebe-d4f7-4d07-b7bb-d7df651b096d.png new file mode 100644 index 0000000000000000000000000000000000000000..e722748042ff18769cd22c3d24e0cbd0d7e38928 Binary files /dev/null and b/docs/handson-tl-py/img/ee49aebe-d4f7-4d07-b7bb-d7df651b096d.png differ diff --git a/docs/handson-tl-py/img/eff1a0b4-d5ea-4ee0-bc7d-9df87102d461.png b/docs/handson-tl-py/img/eff1a0b4-d5ea-4ee0-bc7d-9df87102d461.png new file mode 100644 index 0000000000000000000000000000000000000000..f4c39428defbd0dd3265a8708bcf422be9a27546 Binary files /dev/null and b/docs/handson-tl-py/img/eff1a0b4-d5ea-4ee0-bc7d-9df87102d461.png differ diff --git a/docs/handson-tl-py/img/f08f4ab8-7dca-4fe4-ae07-5d159b65881b.png b/docs/handson-tl-py/img/f08f4ab8-7dca-4fe4-ae07-5d159b65881b.png new file mode 100644 index 0000000000000000000000000000000000000000..5ecbfbb6e83fac8aa319640bdf42c9c66e447549 Binary files /dev/null and b/docs/handson-tl-py/img/f08f4ab8-7dca-4fe4-ae07-5d159b65881b.png differ diff --git a/docs/handson-tl-py/img/f11ab37a-cac5-4045-adb4-19e8ddaf6b2f.png b/docs/handson-tl-py/img/f11ab37a-cac5-4045-adb4-19e8ddaf6b2f.png new file mode 100644 index 0000000000000000000000000000000000000000..30de04113fc50ebc05e152c496a392aca0837c8b Binary files /dev/null and b/docs/handson-tl-py/img/f11ab37a-cac5-4045-adb4-19e8ddaf6b2f.png differ diff --git a/docs/handson-tl-py/img/f1c31eb8-3da3-4746-af69-1851a2bc27cc.png b/docs/handson-tl-py/img/f1c31eb8-3da3-4746-af69-1851a2bc27cc.png new file mode 100644 index 0000000000000000000000000000000000000000..2680b5038bf50094dcf27f8627000a8a6b310522 Binary files /dev/null and b/docs/handson-tl-py/img/f1c31eb8-3da3-4746-af69-1851a2bc27cc.png differ diff --git a/docs/handson-tl-py/img/f1df58da-ec48-4fda-828a-1979a036e221.png b/docs/handson-tl-py/img/f1df58da-ec48-4fda-828a-1979a036e221.png new file mode 100644 index 0000000000000000000000000000000000000000..78af7e3d2804d75506738fe4d5fbb51760243013 Binary files /dev/null and b/docs/handson-tl-py/img/f1df58da-ec48-4fda-828a-1979a036e221.png differ diff --git a/docs/handson-tl-py/img/f2578d55-26a4-4983-9981-c24abff792e5.png b/docs/handson-tl-py/img/f2578d55-26a4-4983-9981-c24abff792e5.png new file mode 100644 index 0000000000000000000000000000000000000000..2e61bc83c013a8d3dccfc183b38447a52433ccb7 Binary files /dev/null and b/docs/handson-tl-py/img/f2578d55-26a4-4983-9981-c24abff792e5.png differ diff --git a/docs/handson-tl-py/img/f366040e-1ec5-4a7b-9d0e-277f00c308e1.png b/docs/handson-tl-py/img/f366040e-1ec5-4a7b-9d0e-277f00c308e1.png new file mode 100644 index 0000000000000000000000000000000000000000..a1192e05b26c1c963543082f0e194adb3fdde485 Binary files /dev/null and b/docs/handson-tl-py/img/f366040e-1ec5-4a7b-9d0e-277f00c308e1.png differ diff --git a/docs/handson-tl-py/img/f443e501-7c1a-4178-83ba-57a4570cac93.png b/docs/handson-tl-py/img/f443e501-7c1a-4178-83ba-57a4570cac93.png new file mode 100644 index 0000000000000000000000000000000000000000..445dc35a1345ef560bc898d12f72a62c9a4a157c Binary files /dev/null and b/docs/handson-tl-py/img/f443e501-7c1a-4178-83ba-57a4570cac93.png differ diff --git a/docs/handson-tl-py/img/f448c97e-6ab7-4718-90ff-cc360122bce3.png b/docs/handson-tl-py/img/f448c97e-6ab7-4718-90ff-cc360122bce3.png new file mode 100644 index 0000000000000000000000000000000000000000..b4a88a2581e0fc93ee7433eb48d63fcd22e8511a Binary files /dev/null and b/docs/handson-tl-py/img/f448c97e-6ab7-4718-90ff-cc360122bce3.png differ diff --git a/docs/handson-tl-py/img/f4a1030f-612c-461f-92cf-cf4f410be996.png b/docs/handson-tl-py/img/f4a1030f-612c-461f-92cf-cf4f410be996.png new file mode 100644 index 0000000000000000000000000000000000000000..345a9facbb43dda5035fbac236d322924b33b411 Binary files /dev/null and b/docs/handson-tl-py/img/f4a1030f-612c-461f-92cf-cf4f410be996.png differ diff --git a/docs/handson-tl-py/img/f4d3d071-bd12-4d75-99c3-f51d640b329d.png b/docs/handson-tl-py/img/f4d3d071-bd12-4d75-99c3-f51d640b329d.png new file mode 100644 index 0000000000000000000000000000000000000000..ae2a092564a0240f98455724c0957d7157860c67 Binary files /dev/null and b/docs/handson-tl-py/img/f4d3d071-bd12-4d75-99c3-f51d640b329d.png differ diff --git a/docs/handson-tl-py/img/f6f9d3f8-d00f-414c-bd48-83905e444b82.png b/docs/handson-tl-py/img/f6f9d3f8-d00f-414c-bd48-83905e444b82.png new file mode 100644 index 0000000000000000000000000000000000000000..7edfdae0251c23428748e7785fe5ab18228adea7 Binary files /dev/null and b/docs/handson-tl-py/img/f6f9d3f8-d00f-414c-bd48-83905e444b82.png differ diff --git a/docs/handson-tl-py/img/f774189e-1543-4caf-b12e-a89dcb004d66.png b/docs/handson-tl-py/img/f774189e-1543-4caf-b12e-a89dcb004d66.png new file mode 100644 index 0000000000000000000000000000000000000000..9b6b02d96d5c49f46713114c10720c0f54936b60 Binary files /dev/null and b/docs/handson-tl-py/img/f774189e-1543-4caf-b12e-a89dcb004d66.png differ diff --git a/docs/handson-tl-py/img/f9c99c79-4b1c-4293-9227-179a6277c4d8.png b/docs/handson-tl-py/img/f9c99c79-4b1c-4293-9227-179a6277c4d8.png new file mode 100644 index 0000000000000000000000000000000000000000..b3ef3fd815427dc7c383d6f2a51deb959cb81a44 Binary files /dev/null and b/docs/handson-tl-py/img/f9c99c79-4b1c-4293-9227-179a6277c4d8.png differ diff --git a/docs/handson-tl-py/img/f9d8bcc5-8454-4231-a9eb-defa4e9761d3.png b/docs/handson-tl-py/img/f9d8bcc5-8454-4231-a9eb-defa4e9761d3.png new file mode 100644 index 0000000000000000000000000000000000000000..efb78d3d5dc593a92d6be1df04840f3cc970cbd0 Binary files /dev/null and b/docs/handson-tl-py/img/f9d8bcc5-8454-4231-a9eb-defa4e9761d3.png differ diff --git a/docs/handson-tl-py/img/f9f07288-c498-4754-98ab-67000c76d58c.png b/docs/handson-tl-py/img/f9f07288-c498-4754-98ab-67000c76d58c.png new file mode 100644 index 0000000000000000000000000000000000000000..027583c2ec3bfd1d732f66abe6875605ba5156ad Binary files /dev/null and b/docs/handson-tl-py/img/f9f07288-c498-4754-98ab-67000c76d58c.png differ diff --git a/docs/handson-tl-py/img/fb2a9e1f-dc9f-47c7-9e67-26b712c100ab.png b/docs/handson-tl-py/img/fb2a9e1f-dc9f-47c7-9e67-26b712c100ab.png new file mode 100644 index 0000000000000000000000000000000000000000..772ebc87b8bef8c5b4ade20e7af3b072906b38a5 Binary files /dev/null and b/docs/handson-tl-py/img/fb2a9e1f-dc9f-47c7-9e67-26b712c100ab.png differ diff --git a/docs/handson-tl-py/img/fcfbb0ab-e892-4392-8372-fcf9dfbaecdf.png b/docs/handson-tl-py/img/fcfbb0ab-e892-4392-8372-fcf9dfbaecdf.png new file mode 100644 index 0000000000000000000000000000000000000000..64cb409d306ecda75bbc0422bf823df9d56d429d Binary files /dev/null and b/docs/handson-tl-py/img/fcfbb0ab-e892-4392-8372-fcf9dfbaecdf.png differ diff --git a/docs/handson-tl-py/img/fd570bbe-898c-40aa-b18b-a884739d3c8d.png b/docs/handson-tl-py/img/fd570bbe-898c-40aa-b18b-a884739d3c8d.png new file mode 100644 index 0000000000000000000000000000000000000000..ac1af4706cfbeea0bc6a84608054dc7fba0ca9dc Binary files /dev/null and b/docs/handson-tl-py/img/fd570bbe-898c-40aa-b18b-a884739d3c8d.png differ diff --git a/docs/handson-tl-py/img/fea9ce1d-e694-44a2-97c6-8d3199f28e9c.png b/docs/handson-tl-py/img/fea9ce1d-e694-44a2-97c6-8d3199f28e9c.png new file mode 100644 index 0000000000000000000000000000000000000000..6905b97e4176641c7ecf1d5001548d6a04fd4f96 Binary files /dev/null and b/docs/handson-tl-py/img/fea9ce1d-e694-44a2-97c6-8d3199f28e9c.png differ diff --git a/docs/handson-tl-py/img/fef839f3-ef74-4306-99fb-df4394da971a.png b/docs/handson-tl-py/img/fef839f3-ef74-4306-99fb-df4394da971a.png new file mode 100644 index 0000000000000000000000000000000000000000..c65f3cba33038635d02f9af4ef35d6809fbd3e96 Binary files /dev/null and b/docs/handson-tl-py/img/fef839f3-ef74-4306-99fb-df4394da971a.png differ diff --git a/docs/handson-tl-py/img/ff28a958-6bcb-4ae9-84f8-8300bffd4aa7.png b/docs/handson-tl-py/img/ff28a958-6bcb-4ae9-84f8-8300bffd4aa7.png new file mode 100644 index 0000000000000000000000000000000000000000..a4860808a3d577accebcde31cc0344a111811ca5 Binary files /dev/null and b/docs/handson-tl-py/img/ff28a958-6bcb-4ae9-84f8-8300bffd4aa7.png differ