一、机器学习基础
有一天,人工智能将像看非洲平原上的化石骨架一样回望我们。 一只生活在尘土中的直立猿,用粗俗的语言和工具灭绝。
——内森·贝特曼(Nathan Bateman),前 Machina(电影节 2014)
这句话似乎夸大了核心内容,难以理解,但是随着技术和科学的进步,谁知道呢? 作为一个物种,我们一直梦想创造出智能的,具有自我意识的机器。 随着研究,技术和计算能力民主化的最新发展,人工智能(AI),机器学习(ML) 深度学习已在技术人员和一般人群中引起了极大的关注和炒作。 尽管好莱坞承诺的未来值得商,,但我们已经开始在日常生活中看到和使用智能系统。 从诸如 Google Now,Siri,Alexa 和 Cortana 之类的智能对话引擎,到无人驾驶汽车,我们正在日常工作中逐渐接受这种智能技术。
随着我们步入学习机器的新时代,重要的是要了解基本概念和概念已经存在了一段时间,并且已经被地球上的聪明人不断改进。 众所周知,全球数据的 90% 是在最近几年中创建的,并且我们将以越来越高的速度继续创建更多的数据。 机器学习,深度学习和人工智能领域帮助我们利用这些海量数据来解决各种现实问题。
本书分为三个部分。 在第一部分中,我们将开始与 AI,ML 和深度学习相关的基本概念和术语,然后是深度学习架构的深入细节。
本章为读者提供了有关 ML 基本概念的快速入门,然后在后续各章中开始进行深度学习。 本章涵盖以下方面:
本书的每一章都基于前几章的概念和技术。 精通 ML 和深度学习基础知识的读者可以选择他们认为必要的主题,但建议顺序阅读这些章节。 您可以在 GitHub 存储库中的Chapter 1
文件夹中快速阅读本章的代码。 可以根据需要参考本章。
我们生活的世界中,我们的日常工作涉及与数字世界的多个接触点。 我们有计算机协助我们进行通讯,旅行,娱乐等。 我们一直无缝使用的数字在线产品(应用,网站,软件等)帮助我们避免了平凡而又重复的任务。 这些软件已由程序员明确使用每条指令进行编程以使这些软件能够执行定义的任务,并使用计算机编程语言(如 C,C++,Python,Java 等)开发。 下图描述了计算设备(计算机,电话等)与带有输入和定义的输出的显式编程的软件应用之间的典型交互:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UOZUT5lA-1681567233308)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/5da17bd5-6922-48a9-b779-3cfaff2c2a45.png)]
传统编程范例
尽管当前的范例已经在帮助我们开发出惊人的复杂软件/系统,以一种非常有效的方式来解决来自不同领域和方面的任务,但它们仍需要有人为此类程序定义并编写明确的规则才能工作。 这些任务对于计算机来说很容易解决,但对人类来说却是困难或费时的。 例如,执行复杂的计算,存储大量数据,通过庞大的数据库进行搜索等等都是可以在定义规则后由计算机有效执行的任务。
然而,还有另一类问题可以由人类直观地解决,但是很难编程。 对象识别,玩游戏等问题对我们来说很自然,但很难用一组规则来定义。 艾伦·图灵(Alan Turing)在他的地标性论文《计算机械和智能》中介绍了图灵测试,讨论了通用计算机以及它们是否能够执行此类任务。
这种体现了通用计算思想的新范式在更广泛的意义上促成了 AI 的产生。 这种新的范式,更好地称为 ML 范式,是计算机或机器从经验(类似于人类学习)中学习来解决任务的方法,而不是经过明确编程才能做到的。
因此,人工智能是研究的一个涵盖领域,而机器学习和深度学习是其中的特定研究子领域。 AI 是一个通用字段,还包括其他子字段,可能涉及也可能不涉及学习(例如,参见符号 AI)。 在本书中,我们将把时间和精力仅用于 ML 和深度学习。 人工智能,机器学习和深度学习的范围可以可视化如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0kauXLRB-1681567233309)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/a731f64c-9ff7-4540-a4b4-caad445dde92.png)]
人工学习的范围,以机器学习和深度学习为子领域
由汤姆·米切尔(Tom Mitchell)提出的 ML 的正式定义解释如下。
如果 P 所衡量的计算机程序在 T 上的表现,会随着经验 E 而提高,该计算机程序据称可以从 E 的经验中以表现指标 P 学习某些任务 T。
该定义以非常简洁的方式很好地捕捉了 ML 的本质。 让我们以现实世界为例,以更好地理解它。 让我们考虑一个任务(T)是识别垃圾邮件。 现在,我们可能会向有关垃圾邮件和非垃圾邮件的系统提供许多示例(或经验),从中可以学习而不是对其进行明确编程。 然后可以在学习到的识别垃圾邮件的任务上对程序或系统的表现进行测量(P)。 有趣,不是吗?
因此,ML 的任务是从训练示例中识别模式,并将这些学习到的模式(或表示形式)应用于新的看不见的数据。 ML 有时也称为浅层学习,因为它学习单层表示形式的性质(在大多数情况下)。 这使我们想到“表示层是什么”和“什么是深度学习”,我们将在后续章节中回答这些问题。 让我们快速了解一下深度学习。
深度学习是机器学习的一个子领域,它涉及从训练示例中学习连续的有意义的表示,以解决给定的任务。 深度学习与人工神经网络紧密相关,人工神经网络由一个接一个的堆叠的多层组成,这些层捕获连续的表示。
如前所述,不要担心是否难以理解和理解,我们将在后续章节中更深入地介绍。
由于我们正在生成和收集的数据量以及更快的计算能力,ML 已成为流行语。 在以下各节中,让我们更深入地研究 ML。
ML 是 AI 的一个流行子领域,涵盖了非常广泛的领域。 如此受欢迎的原因之一是在其能力范围内的复杂算法,技术和方法的综合工具箱。 多年来,该工具箱已经得到开发和改进,并且正在不断研究新的工具箱。 为了明智地理解和使用 ML 工具箱,请考虑以下几种分类方法。
基于人工监督量的分类:
根据数据可用性进行分类:
前面讨论的分类为我们提供了如何组织,理解和利用 ML 算法的抽象视图。 将它们分类的最常见方法是有监督和无监督学习算法。 让我们更详细地介绍这两个类别,因为这将有助于我们开始进一步的高级主题,稍后再进行介绍。
监督学习算法是一类利用数据样本(也称为训练样本)和相应的输出(或标签)推断两者之间映射函数的算法。 推断的映射函数或习得函数是此训练过程的输出。 然后,将习得函数用于正确映射新的和看不见的数据点(输入元素)以测试习得函数的表现。
监督学习算法的一些关键概念如下:
有多种可用的监督学习算法。 根据用例需求,可以将它们主要分类为分类和回归模型。
简而言之,这些算法可帮助我们回答客观问题或是/否预测。 例如,“这些算法在这样的场景下有用吗”或“该肿瘤会癌变吗”等。
形式上,分类算法的主要目标是根据输入数据点预测本质上属于分类的输出标签。 输出标签本质上是分类的; 也就是说,它们每个都属于离散的类或类别。
Logistic 回归,支持向量机(SVM),神经网络,随机森林, K 最近邻(KNN),决策树等是一些流行的分类算法。
假设我们有一个真实的用例来评估不同的汽车模型。 为简单起见,让我们假设该模型可以根据多个输入训练样本预测每种汽车模型的输出是可接受的还是不可接受的。 输入的训练样本具有诸如购买价格,门数,容量(以人数计)和安全性的属性。
除类标签之外的级别将每个数据点表示为可接受或不可接受。 下图描述了即将出现的二分类问题。 分类算法将训练样本作为输入以准备监督模型。 然后,利用该模型来预测新数据点的评估标签:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rXFNFjFU-1681567233309)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/259513b0-c48f-487c-898c-780cf7700b29.png)]
监督学习:用于汽车模型评估的二分类
由于在分类问题中输出标签是离散类,因此如果只有两个可能的输出类,则该任务称为二分类问题,否则称为多类分类。 预测明天是否下雨将是一个二分类问题(输出是“是”或“否”),而从扫描的手写图像中预测数字将是具有 10 个标签(零到九个可能的输出标签)的多类分类。 。
此类监督学习算法有助于我们回答数量或定量类型的问题。 正式而言,回归模型的关键目标是值的估计。 在这种情况下,输出标签本质上是连续的(相对于分类而言是离散的)。
在回归问题的情况下,输入数据点称为自变量或解释变量,而输出称为因变量。 还使用训练数据样本来训练回归模型,该训练数据样本包括输入(或独立)数据点以及输出(或相关)信号。 线性回归,多元回归,回归树等是一些监督式回归算法。
可以基于建模模型如何对因变量和自变量之间的关系建模来进一步分类。
简单线性回归模型适用于单个自变量和单个因变量。 普通最小二乘(OLS)回归是一种流行的线性回归模型。 多元回归或多元回归是只有一个因变量的地方,而每个观察值都是由多个解释变量组成的向量。
多项式回归模型是多元回归的一种特殊情况。 在此,因变量被建模为自变量的第 n 次幂。 由于多项式回归模型拟合或映射因变量和自变量之间的非线性关系,因此这些也称为非线性回归模型。
以下是线性回归的示例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cl16Lg6t-1681567233309)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/6174260e-2892-4fe2-9cb9-a687760ee2a9.png)]
监督学习:线性回归
为了理解不同的回归类型,让我们考虑一个真实的用例,它基于汽车的速度来估计汽车的停止距离。 在这里,根据我们拥有的训练数据,我们可以将停车距离建模为速度的线性函数或汽车速度的多项式函数。 请记住,主要目的是在不过拟合训练数据本身的情况下最大程度地减少误差。
上图描述了线性拟合,而下图描述了同一数据集的多项式拟合:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qi2RTdvj-1681567233309)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/b3ad80ab-a9db-4728-bd61-8a91790e03b5.png)]
监督学习:多项式回归
顾名思义,此类算法无需监督即可学习/推断概念。 与监督学习算法基于包含输入数据点和输出信号的训练数据集推断映射函数不同,无监督算法的任务是在训练数据中查找模式和关系,而训练数据集中没有可用的输出信号。 这类算法利用输入数据集来检测模式,并挖掘规则或组/集群数据点,以便从原始输入数据集中提取有意义的见解。
当我们没有包含相应输出信号或标签的训练集的自由时,无监督算法会派上用场。 在许多实际场景中,可以使用没有输出信号的数据集,很难手动标记它们。 因此,无监督算法有助于弥补这种差距。
与监督学习算法类似,也可以对非监督算法进行分类,以易于理解和学习。 以下是无监督学习算法的不同类别。
分类的无监督等效项称为聚类。 这些算法可以帮助我们将数据点聚类或分组为不同的组或类别,而无需在输入/训练数据集中使用任何输出标签。 这些算法尝试使用固有特征基于某种相似性度量将输入数据集中的模式和关系查找到不同的组中,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ISSlc5Jz-1681567233310)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/a19a6e64-1663-4055-8823-101a8af77049.png)]
无监督学习:将新闻文章聚类
有助于理解群集的真实示例可以是新闻文章。 每天有数百篇新闻文章,每一篇都涉及从政治,体育到娱乐等不同主题。 如上图所示,可以使用聚类来实现将这些物品组合在一起的无监督方法。
有多种执行聚类过程的方法。 最受欢迎的是:
数据和 ML 是最好的朋友,但是越来越多的数据带来了很多问题。 大量的属性或膨胀的特征空间是一个常见问题。 较大的特征空间在分析和可视化数据时会带来问题,以及与训练,内存和空间限制有关的问题。 这也被称为维度诅咒。 由于无监督方法可以帮助我们从未标记的训练数据集中提取见解和模式,因此它们也有助于我们减少维度。
换句话说,无监督方法通过帮助我们从完整的可用列表中选择一组代表性的特征来帮助我们减少特征空间:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FYRAI03K-1681567233310)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/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 清楚地描述了执行任何项目所必需的步骤,过程和工作流,从正式的业务需求到测试和部署解决方案以将数据转化为见解。 数据科学,数据挖掘和 ML 都是要尝试运行多个迭代过程以从数据中提取见解和信息。 因此,我们可以说,分析数据的确是一门艺术,也是一门科学,因为它并非总是无缘无故地运行算法。 许多主要工作涉及了解业务,所投入工作的实际价值以及表达最终结果和见解的正确方法。
数据科学和数据挖掘项目本质上是迭代的,以从数据中提取有意义的见解和信息。 数据科学与科学一样具有艺术性,因此在应用实际算法(再次经过多次迭代)并最终进行评估和部署之前,需要花费大量时间来了解业务价值和手头的数据。
与具有不同生命周期模型的软件工程项目相似,CRISP-DM 可帮助我们从头到尾跟踪数据挖掘和分析项目。 该模型分为六个主要步骤,涵盖从业务和数据理解到评估和最终部署的各个方面,所有这些本质上都是迭代的。 请参见下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-udz1S0jK-1681567233310)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/47410ec4-28c7-4e91-9152-23363f957e63.png)]
描述 ML 项目工作流程的 CRISP-DM 模型
现在让我们更深入地研究六个阶段中的每个阶段,以更好地了解 CRISP-DM 模型。
第一步也是最重要的一步是了解业务。 这一关键步骤始于设置业务环境和问题要求。 正式定义业务需求对于将其转换为数据科学和分析问题陈述至关重要。 此步骤还用于为业务和数据科学团队设置期望和成功标准,使其位于同一页面上并跟踪项目进度。
此步骤的主要交付成果是详细计划,包括主要里程碑,时间表,假设,约束,警告,预期问题和成功标准。
数据收集和理解是 CRISP-DM 框架的第二步。 在这一步中,我们将进行更深入的了解,以了解和分析上一步中形式化的问题陈述的数据。 此步骤开始于调查先前详细项目计划中概述的各种数据源。 然后将这些数据源用于收集数据,分析不同的属性并记录数据质量。 此步骤还涉及通常称为探索性数据分析的内容。
探索性数据分析(EDA)是非常重要的子步骤。 在 EDA 期间,我们分析了数据的不同属性,属性和特征。 我们还将 EDA 期间的数据可视化,以更好地理解和发现以前可能看不见或忽略的模式。 此步骤为后续步骤奠定了基础,因此,这一步骤根本不能忽略。
这是任何数据科学项目中的第三步,也是最耗时的步骤。 一旦我们了解了业务问题并探索了可用数据,便会进行数据准备。 此步骤涉及数据集成,清理,整理,特征选择和特征工程。 首先,最重要的是数据集成。 有时候,可以从各种来源获得数据,因此需要根据某些键或属性进行组合以更好地使用。
数据清理和整理是非常重要的步骤。 这涉及处理缺失值,数据不一致,修复不正确的值以及将数据转换为可摄取格式,以便 ML 算法可以使用它们。
数据准备是最耗时的步骤,占任何数据科学项目总时间的 60-70% 以上。 除了数据集成和处理之外,此步骤还包括根据相关性,质量,假设和约束条件选择关键特征。 这也称为特征选择。 有时候,我们不得不从现有特征中衍生或生成特征。 例如,根据用例要求,从出生日期算起年龄等等。 此步骤称为特征工程,并且根据用例再次需要此步骤。
第四步或建模步骤是进行实际分析和机器学习的地方。 此步骤将在上一步中准备的干净和格式化的数据用于建模目的。 这是一个迭代过程,与数据准备步骤同步工作,因为模型/算法需要具有不同属性集的不同设置/格式的数据。
此步骤涉及选择相关工具和框架,以及选择建模技术或算法。 此步骤包括基于业务理解阶段中确定的期望和标准进行模型构建,评估和模型微调。
一旦建模步骤生成满足成功标准,表现基准和模型评估指标的模型,就需要进行彻底的评估。 在此步骤中,我们将在进行部署阶段之前考虑以下活动:
CRISP-DM 模型的最后一步是部署到生产。 在多次迭代过程中开发,微调,验证和测试的模型将保存起来并准备用于生产环境。 构建了适当的部署计划,其中包括有关硬件和软件要求的详细信息。 部署阶段还包括进行检查和监视方面,以评估生产中的模型的结果,表现和其他指标。
CRISP-DM 模型提供了用于 ML 和相关项目管理的高级工作流。 在本节中,我们将讨论用于处理 ML 项目的技术方面和标准工作流的实现。 简而言之,ML 流水线是一个端到端的工作流程,由数据密集型项目的各个方面组成。 一旦涵盖了业务理解,风险评估以及 ML 或数据挖掘技术选择等初始阶段,我们便会着手推动项目的解决方案空间。 下图显示了具有不同子组件的典型 ML 流水线或工作流:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1oev8MsI-1681567233310)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/f366040e-1ec5-4a7b-9d0e-277f00c308e1.png)]
典型的 ML 流水线
标准 ML 流水线大致包括以下阶段。
数据收集和提取通常是故事的起点。 数据集有各种形式,包括结构化和非结构化数据,这些数据通常包括丢失或嘈杂的数据。 每种数据类型和格式都需要特殊的机制来进行数据处理和管理。 例如,如果一个项目涉及对推文的分析,我们需要使用 Twitter API 并开发机制以提取所需的推文,这些推文通常为 JSON 格式。
其他场景可能涉及已经存在的结构化或非结构化公共数据集或私有数据集,除了仅开发提取机制外,这两种情况都可能需要其他权限。 Sarkar 和他们的合著者在《Python 机器学习》的第 3 章中讨论了与使用各种数据格式有关的相当详细的内容,如果您有兴趣进一步研究,请参阅它。
值得重申的是,这是在整个流水线中花费最大时间的地方。 这是一个相当详细的步骤,涉及基本和重要的子步骤,其中包括:
到目前为止,项目的所有初始步骤都围绕业务环境,需求,风险等。 这是我们实际深入研究收集/可用数据的第一个接触点。 EDA 帮助我们了解数据的各个方面。 在此步骤中,我们将分析数据的不同属性,发现有趣的见解,甚至可视化不同维度上的数据以获得更好的理解。
此步骤可帮助我们收集手头的数据集的重要特征,这不仅在项目的后期阶段很有用,而且还有助于我们在流水线早期识别和/或缓解潜在问题。 我们将在本章后面介绍一个有趣的示例,以使读者了解 EDA 的过程和重要性。
此步骤与将数据转换为可用形式有关。 在大多数情况下,ML 算法无法使用第一步中检索到的原始数据。 形式上,数据整理是将数据从一种形式清除,转换和映射为另一种形式,以便在项目生命周期的后期阶段使用的过程。 此步骤包括缺少数据插补,类型转换,处理重复项和异常值,等等。 为了更好的理解,我们将在用例驱动的章节中介绍这些步骤。
经过预处理和处理后的数据达到了可以被特征工程和提取步骤利用的状态。 在此步骤中,我们利用现有属性来导出和提取上下文/用例特定的属性或特征,这些属性或特征可以在接下来的阶段中被 ML 算法利用。 我们根据数据类型采用不同的技术。
特征工程和提取是一个相当复杂的步骤,因此在本章的后面部分将进行更详细的讨论。
在某些情况下,可用特征的数量过大,会对整个解决方案产生不利影响。 具有大量属性的数据集的处理和处理不仅成为问题,而且还导致解释,可视化等方面的困难。 这些问题被正式称为维度的诅咒。
因此,特征选择可以帮助我们确定可以在建模步骤中使用的代表性特征集,而不会造成太多信息损失。 有多种技术可以执行特征选择。 本章后面的部分将讨论其中的一些。
在建模过程中,我们通常将数据特征提供给 ML 方法或算法并训练模型,通常是为了优化特定的成本函数,在大多数情况下,目的是减少误差并归纳从数据中学到的表示形式。
根据数据集和项目要求,我们应用一种或多种不同的机器学习技术的组合。 这些可以包括有监督的技术(例如分类或回归),无监督的技术(例如聚类),甚至是结合了不同技术的混合方法(如先前在“ML 技术”部分中所讨论的)。
建模通常是一个迭代过程,我们经常利用多种算法或方法,并根据模型评估表现指标来选择最佳模型。 由于这是一本有关迁移学习的书,因此我们将在后续章节中主要基于深度学习构建模型,但是建模的基本原理与 ML 模型非常相似。
开发模型只是从数据中学习的一部分。 建模,评估和调整是迭代步骤,可帮助我们微调和选择表现最佳的模型。
模型基本上是数据的通用表示形式,并且是用于学习该表示形式的基础算法。 因此,模型评估是针对某些标准评估构建模型以评估其表现的过程。 模型表现通常是定义为提供数值以帮助我们确定任何模型的有效性的函数。 通常,会根据这些评估指标对成本或损失函数进行优化以构建准确的模型。
根据所使用的建模技术,我们利用相关的评估指标。 对于有监督的方法,我们通常利用以下技术:
评估无监督方法(例如聚类)的流行指标包括:
请注意,此列表描述了广泛使用的最受欢迎的度量标准,但绝不是模型评估度量标准的详尽列表。
交叉验证也是模型评估过程的重要方面,在该过程中,我们利用基于交叉验证策略的验证集通过调整模型的各种超参数来评估模型表现。 您可以将超参数视为可以用来调整模型以构建有效且表现更好的模型的旋钮。 当我们在随后的章节中使用大量的实际操作示例来评估模型时,这些评估技术的用法和细节将更加清晰。
监督学习算法可帮助我们推断或学习从输入数据点到输出信号的映射。 该学习导致目标或习得函数。 现在,在理想情况下,目标函数将学习输入变量和输出变量之间的精确映射。 不幸的是,没有理想。
正如在介绍监督学习算法时所讨论的那样,我们利用称为训练数据集的数据子集来学习目标函数,然后在称为测试数据集的另一个子集上测试表现。 由于该算法仅看到所有可能数据组合的子集,因此在预测输出和观察到的输出之间会出现误差。 这称为总误差或预测误差:
总误差 = 偏差误差 + 方差误差 + 不可约误差
不可减少的误差是由于噪声,我们对问题的框架,收集数据的方式等导致的固有误差。 顾名思义,这种误差是无法避免的,从算法的角度来看,我们几乎无能为力。
术语偏差是指由学习算法推断目标函数的基础假设。 高偏差表明该算法对目标函数有更多假设,而低偏差表明该假设较少。
由偏差引起的误差只是预期(或平均)预测值与实际观察值之间的差。 为了获得预测的平均值,我们多次重复学习步骤,然后取平均结果。 偏差误差有助于我们了解模型的概括性。 低偏差算法通常是非参数算法,例如决策树,SVM 等,而参数函数(例如线性和逻辑回归)的偏差很高。
方差标志着模型对训练数据集的敏感性。 众所周知,学习阶段依赖于称为训练集的所有可能数据组合的一小部分。 因此,随着训练数据集的变化,方差误差捕获了模型估计值的变化。
低方差表明预测值的变化要少得多,因为基础训练数据集会发生变化,而高方差则指向另一个方向。 非参数算法(例如决策树)具有较高的方差,而参数算法(例如线性回归)的灵活性较差,因此方差较低。
偏差方差权衡是同时减少监督学习算法的偏差和方差误差的问题,这阻止了目标函数泛化到训练数据点之外。 让我们看一下以下插图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F72glgbP-1681567233311)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/bfa1f85c-82ab-4340-bbd0-8ca16ea2286a.png)]
偏差方差权衡
鼓励读者访问以下链接,以更好,更深入地了解偏差方差的权衡方法。
考虑给我们给出一个问题陈述:“给定一个人的身高,确定他/她的体重”。 我们还为训练数据集提供了相应的身高和体重值。 数据如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QXuXJOKI-1681567233311)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/72d4d8c2-aaaa-4e37-bca5-cc30ce55657f.png)]
描绘身高体重数据集的图
请注意,这是一个说明重要概念的示例,在解决实际问题的后续章节中,我们将使用实际案例。
这是监督学习问题的一个实例,更多是回归问题的实例(请参阅为什么?)。 利用该训练数据集,我们的算法将必须学习目标函数,以找到不同个体的身高和体重之间的映射。
根据我们的算法,训练阶段可能会有不同的输出。 假设学习的目标函数如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lV2CItWB-1681567233311)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/f4a1030f-612c-461f-92cf-cf4f410be996.png)]
欠拟合模型
该惰性函数始终预测恒定的输出值。 由于目标函数无法学习数据的基础结构,因此导致欠拟合的情况。 欠拟合模型的预测表现较差。
训练阶段的另一个极端称为过拟合。 过拟合图可以表示如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l46KHvcs-1681567233311)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/f1c31eb8-3da3-4746-af69-1851a2bc27cc.png)]
过拟合模型
这显示了一个目标函数,可以完美地映射训练数据集中的每个数据点。 这就是众所周知的模型过拟合。 在这种情况下,该算法试图了解包括噪声在内的确切数据特征,因此无法可靠地预测看不见的新数据点。
欠拟合和过拟合之间的最佳结合点是我们所说的良好拟合。 可以很好地概括给定问题的模型图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vw0eZIDO-1681567233312)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/12c9ca72-544a-460a-8d0c-023b1eda7747.png)]
很好的概括
可以在看不见的数据点以及训练数据上表现良好的学习能力被称为泛化能力。 因此,泛化指的是基于在训练阶段学到的概念,目标函数在看不见的数据上执行得如何。 上图描绘了很好的概括拟合。
准备和评估模型与调整模型一样重要。 与为我们提供标准算法集的不同 ML 框架/库一起工作,我们几乎从来没有立即使用它们。
ML 算法具有不同的参数或旋钮,可以根据项目要求和不同的评估结果进行调整。 通过遍历超参数或元参数的不同设置来获得更好的结果,模型调整可以起作用。 超参数是高级抽象上的旋钮,它是在学习过程开始之前设置的。
这与模型级别参数不同,模型级别参数在训练阶段期间学习。 因此,模型调整也称为超参数优化。
网格搜索,随机超参数搜索,贝叶斯优化等是执行模型调整的流行方法。 尽管模型调整非常重要,但过度调整可能会对学习过程产生不利影响。 在“偏差方差权衡”部分中讨论了与过度调整过程有关的一些问题。
一旦完成了模型开发,评估和调整,并进行了多次迭代以改善结果,那么模型部署的最后阶段就到了。 模型部署负责各个方面,例如模型持久性,通过不同的机制(例如 API 端点)将模型公开给其他应用,以及制定监视策略。
我们生活在一个瞬息万变的世界,每时每刻都在变化,关于数据和与用例相关的其他因素也是如此。 我们必须制定监控策略,例如定期报告,日志和测试,以检查解决方案的表现并在需要时进行更改。
机器学习流水线与软件工程以及数据科学和机器学习一样重要。 我们简要概述并讨论了典型流水线的不同组成部分。 根据特定的用例,我们修改了标准流水线以适应需要,同时确保我们不会忽略已知的陷阱。 在接下来的部分中,我们将通过实际示例和代码片段更详细地了解典型 ML 流水线的几个组件。
当我们开始任何 ML 项目时,EDA 就是我们执行的前几个任务之一。 正如在“CRISP-DM”一节中所讨论的那样,数据理解是发现有关数据的各种见解并更好地理解业务需求和上下文的重要步骤。
在本节中,我们将使用一个实际的数据集,并使用pandas
作为我们的数据处理库以及seaborn
进行可视化来执行 EDA。 Python 笔记本game_of_thrones_eda.ipynb
中提供了完整的代码段和此分析的详细信息。
首先,我们导入所需的库并按照以下代码片段所示设置配置:
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 的《冰与火之歌》小说系列的改编。 作为一个受欢迎的系列,它吸引了许多人的注意,并且数据科学家也不排除在外。 该笔记本在 Myles O’Neill 增强的 Kaggle 数据集上显示了 EDA(更多详细信息。 该数据集基于多个人收集并贡献的多个数据集的组合。 在此分析中,我们使用了battles.csv
。 原始战斗数据由克里斯·阿尔邦(Chris Albon)提供; 更多详细信息,请参见这里。
以下代码段使用pandas
加载battles.csv
文件:
In [2]: battles_df = pd.read_csv('battles.csv')
数据集如以下屏幕快照所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rHi5FxIH-1681567233312)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/de9875f7-8674-4f2f-909a-828daa8bd1c0.png)]
权力的游戏的Battles.csv
中的示例行
我们可以分别使用pandas
工具shape
,dtypes
和describe()
查看行的总数,每个属性的数据类型以及数字属性的常规统计信息。 我们有 38 场战斗的数据,其中 25 项属性描述了每一场。
让我们了解幻想世界多年来战斗的分布情况。 以下代码段绘制了此分布的条形图:
In [3]: sns.countplot(y='year',data=battles_df)
...: plt.title('Battle Distribution over Years')
...: plt.show()
下图显示,在 299 年中,战斗次数最多,其次分别是 300 和 298:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-75dEv803-1681567233312)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/3a4330e8-8f0c-440a-aa0c-b0faf5f172d9.png)]
多年来的战斗分配
在这个幻想的土地上有不同的地区,在可以想象的每个地方都发生着战斗。 但是,有趣的是看看是否有任何首选区域。 以下代码段有助于我们准确地回答此问题:
In [4]: sns.countplot(x='region',data=battles_df)
...: plt.title('Battles by Regions')
...: plt.show()
以下图表可帮助我们确定里弗兰兹战斗最多,其次是北部和西部:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZbxEq4BA-1681567233312)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/be3c2da1-6403-4145-8e3f-bc0c2b09bfb6.png)]
地区战
还要注意的另一件有趣的事是,只有一场战役超越了墙面(扰流板警报:请稍后关注)。
我们可以使用不同的分组依据进行类似的分析,以了解例如主要死亡人数或每个地区的捕获数,等等。
我们继续前进,看看哪个国王攻击最多。 我们使用饼图将其可视化,以了解每个参与的国王所进行的战斗所占的百分比。 请注意,我们基于攻击国王进行此分析。 防御王也可以执行类似的分析。 以下代码段准备了一个饼图,以显示每个进攻国王的战斗份额:
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-lgC4f6RF-1681567233312)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/d35c4569-5d5f-4fd3-8433-9e3b2ee1790f.png)]
每个进攻国王的战斗份额
韦斯特罗斯和埃索斯的土地充满危险,遍布敌人和威胁。 让我们稍微分析一下数据,以了解每位国王在多少次获胜者。 由于国王既可以保卫自己的土地,也可以争取权力,因此看到保卫和进攻的胜利也很有趣。 以下代码段有助于我们准备堆积的条形图,以分析每位国王的进攻和防守胜利:
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()
前面的代码段计算出攻击时每位国王的获胜次数,然后计算出防守时每位国王的获胜次数。 然后,我们将两个结果合并,并使用堆叠的条形图绘制相同的结果。 结果显示在下图中:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fPmvOqfL-1681567233313)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/e6b49a78-6a3d-4f24-9cb0-ab2b86e2591b.png)]
每位国王获胜的次数
上图清楚地表明,马拉松男孩在进攻和防守中获胜的次数最多。 到目前为止,他们似乎很幸运。 Robb Stark 是第二成功的国王,当然红色婚礼发生了。
数据集还包含描述所涉及房屋的数量,战斗指挥官和军队规模的属性。 我们可以进行类似且更深入的分析,以更好地理解战斗。 我们鼓励读者尝试其中一些作为练习,并检查 Python 笔记本中的更多指针。
在结束本节之前,让我们尝试确定铁王座之战中的敌人。 尽管粉丝已经对此有所了解,但让我们看看数据对此有何评论。 以下代码段可帮助我们回答此问题:
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-M3bOxqVZ-1681567233313)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/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 算法中摄取数字数据的事实。 但是,这并不意味着数字数据不需要其他处理和特征工程步骤。
有多种技术可以从数值数据中提取和工程化特征。 让我们看一下本节中的一些技术:
笔记本feature_engineering_numerical_and_categorical_data.ipynb
中提供了代码片段,以更好地理解数字数据的特征工程。
常见的另一类重要数据是类别数据。 类别特征具有离散值,这些离散值属于一组有限的类。 这些类可以表示为文本或数字。 根据类别的顺序,类别特征分别称为标称和序数。
标称特征是具有有限值集合但没有任何自然顺序的那些类别特征。 例如,天气季节,电影类型等都是标称特征。 具有有限类集并具有自然顺序的类别特征称为序数特征。 例如,星期几,着装大小等都是常规的。
通常,特征工程中的任何标准工作流程都涉及将这些类别值转换为数字标签的某种形式,然后在这些值上应用某种编码方案。 流行的编码方案简要介绍如下:
笔记本feature_engineering_numerical_and_categorical_data.ipynb
中提供了代码片段,以更好地理解类别数据的特征工程。
图像或视觉数据是丰富的数据源,可以使用 ML 算法和深度学习解决几个用例。 图像数据提出了很多挑战,需要经过仔细的预处理和转换,然后才能被任何算法使用。 对图像数据执行特征工程的一些最常见方法如下:
m, n, c
)矩阵,其中m
代表行数,n
代表列数,c
指向颜色通道(例如 R,G 和 B)。 然后可以根据算法和用例的要求将这种矩阵转换为不同的形状。到目前为止讨论的图像数据和其他类型的特征提取方法需要大量时间,精力和领域知识。 这种特征提取有其优点和局限性。
最近,人们已经研究了深度学习,特别是卷积神经网络(CNN),并将其用作自动特征提取器。 CNN 是针对图像数据进行优化的深度神经网络的特例。 卷积层是任何 CNN 的核心,它们基本上会在图像的高度和宽度上应用滑动滤镜。 像素值与这些滤镜的点积会生成跨多个周期学习的激活图。 在每个层次上,这些卷积层都有助于提取特定特征,例如边缘,纹理,角等。
深度学习和 CNN 还有很多,但是为了简单起见,让我们假设 CNN 在每一层都可以帮助我们自动提取不同的低级和高级特征。 反过来,这使我们免于手动执行特征提取。 我们将在接下来的章节中更详细地研究 CNN,并了解它们如何帮助我们自动提取特征。
数值和分类特征就是我们所谓的结构化数据类型。 它们在 ML 工作流中更易于处理和利用。 文本数据是非结构化信息的重要来源之一,同样重要。 文本数据提出了与句法理解,语义,格式和内容有关的多个挑战。 文本数据还提出了转换为数字形式的问题,然后才能被 ML 算法使用。 因此,在进行文本数据的特征工程之前,需要进行严格的预处理和清理步骤。
在进行任何特征提取/工程设计之前,文本数据需要仔细而勤奋的预处理。 预处理文本数据涉及多个步骤。 以下是一些最广泛使用的文本数据预处理步骤的列表:
在与用例有关的章节中,我们将详细介绍大多数技术。 为了更好地理解,读者可以参考《Python 实用机器学习》的第 4 章和第 7 章(Sarkar 及其合著者,Springer,2017 年)。
通过上一节中提到的方法对文本数据进行正确处理后,我们就可以利用以下一些技术来进行特征提取和转换为数值形式。 Jupyter 笔记本feature_engineering_text_data.ipynb
中提供了可更好地理解文本数据特征的代码片段:
N
维度上的向量,其中N
表示预处理语料库中所有可能的单词,向量的每个组成部分表示单词的存在或其频率。
tfidf(w, D) = tf(W, D) * idf(w, D)
在这里,tfidf(w, D)
表示每个单词w
在文档D
中的 TF-IDF 分数, tf(w, D)
是中每个单词w
在文档D
中的词频,idf(w, D)
表示逆文档频率,计算为语料库C
中总文档数除以w
所在的文档数的对数转换。
除了词袋和 TF-IDF 外,还有其他转换,例如 N-gram 袋,以及单词嵌入,例如 Word2vec,GloVe 等。 我们将在后续章节中详细介绍其中的几个。
特征提取和工程设计的过程可帮助我们从基础数据集中提取特征并生成特征。 在某些情况下,这会导致大量输入要处理的算法。 在这种情况下,怀疑输入中的许多特征可能是多余的,并可能导致复杂的模型甚至过拟合。 特征选择是从可用/生成的完整特征集中识别代表性特征的过程。 预期所选特征集将包含所需信息,以使算法能够解决给定任务而不会遇到处理,复杂性和过拟合的问题。 特征选择还有助于更好地理解建模过程中使用的数据,并加快处理速度。
特征选择方法大致可分为以下三类:
特征选择是构建 ML 系统过程中的重要方面。 如果不谨慎处理,它也是造成系统偏差的主要来源之一。 读者应注意,应该使用与训练数据集分开的数据集来进行特征选择。 将训练数据集用于特征选择将始终导致过拟合,而将测试集用于特征选择则会高估模型的表现。
最受欢迎的库提供了多种特征选择技术。 诸如scikit-learn
之类的库提供了开箱即用的这些方法。 在后面的章节中,我们将看到并利用其中的许多内容。
在任何旅程中,了解概念和技术的牢固基础和共同基础都非常重要。 通过本章有关机器学习基础知识的章节,我们试图实现这一目标。 在开始学习深度学习,迁移学习和更高级的概念之前,必须为 ML 概念奠定坚实的基础。 在本章中,我们涵盖了相当多的基础,并提供了更详细地研究概念的重要指示。
我们通过理解机器学习为何如此重要以及它是一个完全不同的范例来开始本章。 我们简要讨论了 AI,机器学习和深度学习之间的关系。 然后,本章继续介绍了不同的机器学习技术,例如有监督,无监督和强化学习。 我们详细讨论了通常使用哪些不同的监督和非监督方法。
本章还简要介绍了用于 ML 项目工作流程的 CRISP-DM 模型以及 ML 流水线。 我们还讨论了《权力的游戏》幻想世界中战斗数据集的 EDA,以应用不同的概念并了解 EDA 的重要性。 在本章的最后,介绍了特征提取和工程以及特征选择。
在接下来的章节中,我们将以这些概念为基础,并最终在涉及不同实际使用案例的章节中应用所学知识。 欢迎登机!
本章从深度学习真正含义的最基本基础开始,然后深入到围绕神经网络的其他基本概念和术语,深入探讨了深度学习的基本知识。 将向读者概述神经网络的基本构建模块,以及如何训练深度神经网络。 涵盖模型训练的概念,包括激活函数,损失函数,反向传播和超参数调整策略。 这些基础概念对于正在尝试深度神经网络模型的初学者和经验丰富的数据科学家都将有很大的帮助。 我们特别关注如何建立具有 GPU 支持的强大的基于云的深度学习环境,以及设置内部深度学习环境的技巧。 对于希望自己构建大规模深度学习模型的读者来说,这将非常有用。 本章将涵盖以下主题:
在机器学习(ML)中,我们尝试自动发现用于将输入数据映射到所需输出的规则。 在此过程中,创建适当的数据表示形式非常重要。 例如,如果我们要创建一种将电子邮件分类为垃圾邮件/火腿的算法,则需要用数字表示电子邮件数据。 一个简单的表示形式可以是二元向量,其中每个组件从预定义的单词表中描述单词的存在与否。 同样,这些表示是与任务相关的,也就是说,表示可能会根据我们希望 ML 算法执行的最终任务而有所不同。
在前面的电子邮件示例中,如果我们要检测电子邮件中的情感,则不必标识垃圾邮件/火腿,而更有用的数据表示形式可以是二元向量,其中预定义词汇表由具有正极性或负极性的单词组成。 大多数 ML 算法(例如随机森林和逻辑回归)的成功应用取决于数据表示的质量。 我们如何获得这些表示? 通常,这些表示是人为制作的特征,通过做出一些明智的猜测来进行迭代设计。 此步骤称为特征工程,是大多数 ML 算法中的关键步骤之一。 支持向量机(SVM)或一般的内核方法,试图通过将数据的手工表示转换为更高维度的空间来创建更相关的数据表示,使得使用分类或回归来解决 ML 任务变得容易。 但是,SVM 很难扩展到非常大的数据集,并且在诸如图像分类和语音识别等问题上并不成功。 诸如随机森林和梯度提升机(GBMs)之类的集合模型创建了一组弱模型,这些模型专门用于很好地完成小任务,然后将这些弱模型以一些方式组合来产生最终输出。 当我们有非常大的输入尺寸时,它们工作得很好,而创建手工制作的特征是非常耗时的步骤。 总而言之,所有前面提到的 ML 方法都以浅浅的数据表示形式工作,其中涉及通过一组手工制作的特征进行数据表示,然后进行一些非线性转换。
深度学习是 ML 的一个子字段,在其中创建数据的分层表示。 层次结构的较高级别由较低级别的表示形式组成。 更重要的是,通过完全自动化 ML 中最关键的步骤(称为特征工程),可以从数据中自动学习这种表示层次。 在多个抽象级别上自动学习特征允许系统直接从数据中学习输入到输出的复杂表示形式,而无需完全依赖于人工制作的特征。
深度学习模型实际上是具有多个隐藏层的神经网络,它可以帮助创建输入数据的分层层次表示。 之所以称为深度,是因为我们最终使用了多个隐藏层来获取表示。 用最简单的术语来说,深度学习也可以称为分层特征工程(当然,我们可以做更多的事情,但这是核心原理)。 深度神经网络的一个简单示例可以是具有多个隐藏层的多层感知器(MLP)。 下图中考虑基于 MLP 的人脸识别系统。 它学习到的最低级别的特征是对比度的一些边缘和图案。 然后,下一层能够使用那些局部对比的图案来模仿眼睛,鼻子和嘴唇。 最后,顶层使用这些面部特征创建面部模板。 深度网络正在组成简单的特征,以创建越来越复杂的特征,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eF14sl5y-1681567233313)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/32d3a718-5dc8-4ced-a248-bf868068ea3c.png)]
为了理解深度学习,我们需要对神经网络的构建模块,如何训练这些网络以及如何将这样的训练算法扩展到非常大的深度网络有一个清晰的了解。 在深入探讨有关神经网络的更多细节之前,让我们尝试回答一个问题:为什么现在要进行深度学习? 神经网络的理论,甚至是卷积神经网络(CNN)都可以追溯到 1990 年代。 他们之所以变得越来越受欢迎的原因归结于以下三个原因:
深度学习广泛普及和采用的主要原因之一是 Python 深度学习生态系统,它由易于使用的开源深度学习框架组成。 但是,考虑到新框架如何不断发布以及旧框架将要寿终正寝,深度学习的格局正在迅速变化。 深度学习爱好者可能知道 Theano 是由 Yoshua Bengio 领导的 MILA 创建的第一个也是最受欢迎的深度学习框架。 不幸的是,最近宣布,在 Theano 的最新版本(1.0)于 2017 年发布之后,对 Theano 的进一步开发和支持将结束。因此,了解那里可以利用哪些框架来实现和解决问题至关重要。 深度学习。 这里要记住的另一点是,几个组织本身正在建立,获取和启动这些框架(通常试图在更好的功能,更快的执行等方面相互竞争),以使每个人都受益。 下图展示了截至 2018 年最受欢迎的一些深度学习框架:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d7p6aSMh-1681567233313)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/7ff92d2e-11bf-4c54-b366-f0f8d512d881.png)]
您还可以在迈向数据科学了解更多。 让我们简要看看一些最受欢迎的深度学习框架:
tf.contrib
模块,该模块包含各种实验功能,包括 Keras API 本身!
上面的框架列表绝对不是深度学习框架的详尽列表,但是应该使您对深度学习领域中的内容有个很好的了解。 随意探索这些框架,并根据最适合您的情况选择任何一个。
永远记住,有些框架的学习曲线很陡峭,所以如果花时间学习和利用它们,不要灰心。 尽管每个框架都有各自的优点和缺点,但您应始终将更多的精力放在要解决的问题上,然后利用最适合解决问题的框架。
深度学习在带有 CPU 的标准单 PC 设置中效果很好。 但是,一旦您的数据集开始增加大小,并且模型架构开始变得更加复杂,您就需要开始考虑在强大的深度学习环境中进行投资。 主要期望是该系统可以有效地构建和训练模型,花费较少的时间来训练模型,并且具有容错能力。 大多数深度学习计算本质上是数百万个矩阵运算(数据表示为矩阵),并且可以并行进行快速计算。 事实证明,GPU 在这方面可以很好地工作。 您可以考虑建立一个强大的基于云的深度学习环境,甚至是一个内部环境。 让我们看看如何在本节中建立一个强大的基于云的深度学习环境。
涉及的主要组件如下:
让我们更详细地研究这些组件中的每个组件,并逐步执行过程,以帮助您建立自己的深度学习环境。
如今,有多家云提供商的价格可承受且具有竞争力。 我们希望利用平台即服务(PaaS)功能来管理数据,应用和基本配置。
下图显示了一些流行的云提供商:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ug8OpLlD-1681567233314)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/a1ec2d70-f56a-42f8-8ac8-08a88b943061.png)]
受欢迎的提供商包括亚马逊的 AWS,微软的 Azure 和 Google 的 Google 云平台(GCP)。 就本教程和本书而言,我们将利用 AWS。
您需要获取一个 AWS 账户才能执行本节中的其余步骤。 如果您还没有帐户,请转到这里创建一个帐户。 准备就绪后,您可以登录这里来登录您的帐户并导航到 AWS EC2 控制面板,该工具利用了弹性计算云(EC2)服务,这是 Amazon 云计算服务的基础。 到达那里后,请记住选择一个您选择的区域(我通常与美国东部一起去),然后单击“启动实例”以启动在云上创建新虚拟服务器的过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BQGGy2gz-1681567233314)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/38d54157-0b1a-4a50-bced-6460281a2b6f.png)]
单击启动实例按钮应带您到该页面,以选择您自己的 Amazon Machine Image(AMI)。 通常,AMI 由构建虚拟服务器所需的各种软件配置组成。 它包括以下内容:
我们将利用专门用于深度学习的预构建 AMI,因此我们不必花时间进行额外的配置和管理。 前往 AWS Marketplace 并选择深度学习 AMI(Ubuntu):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ncC9ilsy-1681567233314)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/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-JTcRIaG9-1681567233314)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/55556fe2-bd04-47ea-a9c6-9660475b4950.png)]
接下来是配置实例详细信息。 除非希望启动多个实例,指定子网首选项以及指定关闭行为,否则可以保留默认设置。
下一步涉及添加存储详细信息。 通常,您具有根卷,可以在根卷中根据需要增加其大小,并添加额外的弹性块存储(EBS)卷以增加磁盘空间。
然后,我们看一下是否需要添加标签(区分大小写和键值对)。 目前我们不需要这个,所以我们跳过它。
我们将重点放在配置安全组的下一步上,特别是如果您想通过利用功能强大的 Jupyter 笔记本从外部访问深度学习设置。 为此,我们创建一个新的安全组并创建一个 Custom TCP 规则以打开并启用对端口8888
的访问,如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cSnE3RMD-1681567233314)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/79fc8d9c-a2fb-49d6-8510-922dafbd2317.png)]
请注意,此规则通常允许任何 IP 监听您实例(我们将在其中运行 Jupyter 笔记本的实例)上的端口(8888
)。 如果需要,可以更改此设置,仅添加特定 PC 或笔记本电脑的 IP 地址,以提高安全性。 除此之外,我们稍后还将为 Jupyter 笔记本添加一个额外的密码保护功能,以提高安全性。
最后,您将需要通过创建密钥对(公钥和私钥)来启动实例,以安全地连接到实例。 如果您没有现有的密钥对,则可以创建一个新的密钥对,将私钥文件安全地存储到磁盘上,然后启动实例,如以下屏幕快照所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ntwAkMIp-1681567233315)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/496b8ebd-2671-47eb-9222-c5ba6849f120.png)]
请注意,虚拟服务器启动和启动可能需要几分钟,因此您可能需要稍等片刻。 通常,您可能会发现由于帐户的限制或容量不足而导致实例启动失败。
如果遇到此问题,您可以请求增加使用的特定实例类型的限制(在我们的例子中为p2.xlarge
):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NLZ2dCK7-1681567233315)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/75eb8b32-425b-4c71-aa65-fcf126587aca.png)]
通常,AWS 会在不到 24 小时内响应并批准您的请求,因此您可能需要稍等片刻才能获得批准,然后才可以启动实例。 启动实例后,您可以签出“实例”部分并尝试连接到该实例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T5VzfKEZ-1681567233315)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/2c2a21b9-8c3c-4008-802f-2ed5354d6f91.png)]
您可以使用本地系统中的命令提示符或终端(之前已存储了先前的私有 AWS 密钥)来立即连接到实例:
[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 笔记本的功能在虚拟服务器上进行分析和深度学习建模,而无需始终在终端上进行编码。 首先,我们需要设置 SSL 证书。 让我们创建一个新目录:
ubuntu@ip:~$ mkdir ssl
ubuntu@ip:~$ cd ssl
ubuntu@ip:~/ssl$
进入目录后,我们将利用 OpenSSL 创建新的 SSL 证书:
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 笔记本中添加基于密码的安全性的附加层。 为此,我们需要修改 Jupyter 的默认配置设置。 如果您没有 Jupyter 的config
文件,则可以使用以下命令生成它:
$ jupyter notebook --generate-config
要为笔记本计算机启用基于密码的安全性,我们需要首先生成一个密码及其哈希。 我们可以如下利用Ipython.lib
中的passwd()
函数:
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
文件,如下所示:
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:
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-pGoZcP3W-1681567233315)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/9da92d67-d3ef-48f5-9ea0-369abb747e48.png)]
如果出现错误,请按照以下步骤安装 Nvidia GPU 驱动程序。 切记根据您使用的 OS 使用其他驱动程序链接。 我有一个较旧的 Ubuntu 14.04 AMI,为此,我使用了以下命令:
# 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 硬件的全部功能的工具。 您可以在这个页面上找到有关 CUDA 的更多信息并下载工具包。
请记住,CUDA 非常特定于版本,并且我们的 Python 深度学习框架的不同版本仅与特定 CUDA 版本兼容。 我将在本章中使用 CUDA 8。 如果已经为您安装了 CUDA,并且与服务器上的深度学习生态系统一起正常工作,请跳过此步骤。
要安装 CUDA,请运行以下命令:
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 的深度学习模型。 您可以在这个页面上找到有关 cuDNN 的更多信息。 让我们使用以下命令安装 cuDNN:
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
的末尾:
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 发行版一起安装。 但是,如果它不存在,您可以始终参考这里以根据 Python 和 OS 版本下载您选择的发行版。 通常,我们使用 Linux / Windows 和 Python3 并利用本书中的 TensorFlow 和 Keras 深度学习框架。 在 AWS AMI 中,可能会安装不兼容的框架版本,这些框架版本不适用于 CUDA,或者可能是纯 CPU 版本。 以下命令安装 TensorFlow 的 GPU 版本,该版本在 CUDA 8 上最有效:
# 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
文件:
ubuntu@ip:~$ sudo pip install keras --upgrade
ubuntu@ip:~$ sudo pip3 install keras --upgrade
ubuntu@ip:~$ rm ~/.keras/keras.json
现在,我们几乎准备开始利用云上的深度学习设置。 紧紧抓住!
我们真的不想一直坐在服务器上的终端并在其中进行编码。 由于我们要利用 Jupyter 笔记本进行交互式开发,因此我们将从本地系统访问云服务器上的笔记本。 为此,我们首先需要在远程实例上启动 Jupyter 笔记本服务器。
登录到您的虚拟服务器并启动 Jupyter 笔记本服务器:
[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).
现在,我们需要在本地实例上启用端口转发,以便从本地计算机的浏览器访问服务器笔记本。 利用以下语法:
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
。 以下是我用于设置的内容:
[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
,我们将其转发到虚拟服务器中的远程笔记本服务器。 确保您在地址中使用https
,否则会收到 SSL 错误。
如果到目前为止您已正确完成所有操作,则应在浏览器中看到一个警告屏幕,并且按照以下屏幕截图中的步骤进行操作,则在任何笔记本上工作时,都应该会看到熟悉的 Jupyter 用户界面:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ble9W7xt-1681567233316)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/484ebcb5-b14e-4c76-945e-b6562391ba5f.png)]
您可以放心地忽略“您的连接不是私人警告”; 之所以显示它,是因为我们自己生成了 SSL 证书,并且尚未得到任何可信机构的验证。
最后一步是确保一切正常,并且我们的深度学习框架正在利用我们的 GPU(我们需要按小时支付!)。 您可以参考Test GPU enabling.ipynb
Jupyter 笔记本来测试所有代码。 我们将在这里详细介绍。 我们首先要验证的是keras
和tensorflow
是否已正确加载到我们的服务器中。 可以通过如下导入它们来验证:
import keras
import tensorflow
Using TensorFlow backend.
如果您看到前面的代码加载没有错误,那就太好了! 否则,您可能需要追溯以前执行的步骤,并在线搜索要获取的特定错误; 查看每个框架的 GitHub 存储库。
最后一步是检查tensorflow
是否已启用以使用我们服务器的 Nvidia GPU。 您可以使用以下测试对此进行验证:
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 来构建深度学习模型。 关于硬件,特别强调以下方面:
您不应该忽略的其他事项包括主板,电源,坚固的外壳和散热器。
设置完钻机之后,对于软件配置,您可以重复上一部分中的所有步骤,但不包括云设置,您应该一切顺利!
让我们尝试熟悉一下神经网络背后的一些基本概念,这些基本概念使所有深度学习模型都获得成功!
线性神经元是深度神经网络的最基本组成部分。 可以如下图所示。 在这里,X = {x1, ... ,xn}
代表输入向量, w[i]
是神经元的权重。 给定一个包含一组输入目标值对的训练集,线性神经元尝试学习一种线性变换,该变换可以将输入向量映射到相应的目标值。 基本上,线性神经元通过线性函数W^T x = y
近似输入输出关系:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1TgiUQU7-1681567233316)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/9c4a31dd-9384-4d77-9229-5a45c60b24f9.png)]
简单线性神经元和简单非线性神经元的示意图
让我们尝试用这个简单的神经元为玩具问题建模。 员工 A 从自助餐厅购买午餐。 他们的饮食包括鱼,薯条和番茄酱。 他们每个人得到几个部分。 收银员只告诉他们一顿饭的总价。 几天后,他们能算出每份的价格吗?
好吧,这听起来像一个简单的线性编程问题,可以很容易地通过解析来解决。 让我们使用前面的线性神经单元来表示这个问题。 在这里,X = {x[fish], x[ketchup], x[chips]}
和我们有相应的权重(w[fish], w[ketchup], w[chips])
。
每个进餐价格对各部分的价格给出线性约束:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-efhO4WUD-1681567233316)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/04c9762f-de07-4d2a-885b-beee4afb50ae.png)]
假设t[n]
为真实价格,y[n]
由我们的模型估计的价格,由前面的线性方程式给出。 目标与我们的估计之间的剩余价格差为t[n] - y[n]
。 现在,不同餐点的这些残差可以为正或负,并且可以抵消,从而使总体误差为零。 处理此问题的一种方法是使用平方和残差:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A2sgCpNW-1681567233316)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/a7a8c6fa-4111-49ce-bcd9-4c63458b427c.png)]
如果我们能够最大程度地减少此误差,则可以对每件商品的一组权重/价格进行很好的估计。 因此,我们得出了一个优化问题。 让我们首先讨论一些解决优化问题的方法。
优化基本上涉及最小化或最大化某些函数f(x)
,其中x
是数值向量或标量。 在此,f(x)
被称为目标函数或准则。 在神经网络中,我们称其为成本函数,损失函数或误差函数。 在前面的示例中,我们要最小化的损失函数为E
。
假设我们有一个函数y = f(x)
,其中x
和y
是实数。 此函数的导数告诉我们此函数如何随x
的微小变化而变化。 因此,可以通过无穷大地更改x
来使用导数来减小函数的值。 假设对于x
,f'(x) > 0
。 这意味着,如果我们沿着x
的正数增加x
,则f(x)
将会增加,因此对于足够小的ε
,f(x-ε) < f(x)
。 注意f(x)
可以通过在导数的相反方向以小步长移动x
来减少:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b93ZwRf3-1681567233316)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/1f3da55c-feb9-4e0a-80b3-65112938b1ab.png)]
函数值沿导数的相反方向或相反方向的变化方式
如果导数f'(x) < 0
,则导数不提供信息,我们需要朝哪个方向移动以达到函数最小值。 在局部最优(最小/最大)时,导数可以为零。 如果x
处的函数f(x)
的值小于所有相邻点,则将点称为局部最小值。 同样,我们可以定义一个局部最大值。 某些点既不能是最大值,也不能是最小值,但是导数f'(x)
在这些点上为零。 这些称为鞍点。 下图说明了f'(x) = 0
的三种情况:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xE8s86Ac-1681567233317)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/c7eb65b7-41d2-4873-943e-07764f579812.png)]
单个变量函数的最小,最大和鞍点。 在所有三个突出显示的点上导数f'(x) = 0
在x
的所有可能值中达到f
的最小值的点称为全局最小值。 一个函数可以具有一个或多个全局最小值。 可能存在局部最小值,而不是全局最小值。 但是,如果函数是凸函数,则可以保证它只有一个全局最小值,而没有局部最小值。
通常,在 ML 中,我们希望最小化几个变量f
的实值函数:R^n -> R
。 几个变量的实值函数的一个简单示例是热板温度函数f(x1, x2) = 50 - x1^2 - 2x2^2
,其在板上的坐标为x = (x1, x2)
。 在深度学习中,我们通常最小化损失函数,该函数是多个变量(例如神经网络中的权重)的函数。 这些函数具有许多局部最小值,许多鞍点被非常平坦的区域包围,并且它们可能具有也可能没有任何全局最小值。 所有这些使得优化此类函数非常困难。
几个变量的函数的导数表示为偏导数,当我们更改其中一个输入变量x[i]
,并保持其他不变时,它将衡量函数的变化率。 关于所有变量的偏导数向量称为f
的梯度向量,用∇f
表示。 我们还可以找出函数相对于任意方向v
(单位向量)的变化速度。 这是通过在单位向量v
,即点积∇f · v
的方向上投影梯度向量∇f
来计算的。 这在v
方向上被称为f
的定向导数,通常用∇[v]
表示。 为了使f
最小化,我们需要找到一个方向u
,在其中要更改x
,以使f
的值最大程度地减小。
令x[a]
为非常接近x
的点,即||x - x[a]||
非常小。 首先,泰勒级数围绕x
的阶展开式为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W2EY1Voc-1681567233317)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/c7b1a382-402e-4cd3-8c9a-783da142e863.png)]
上式中的最后一项对于x[a]
足够接近x
可以忽略。 第二项表示f
沿x[a] - x
的方向导数。 这里有:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G0E1LIrW-1681567233317)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/323c9d93-2e26-419b-9ed4-16099e6a5350.png)]
因此,如果cos(θ)
最小,则f(x)
最大减小,即 -1,如果θ = π
,即x[a] - x
应该指向与梯度向量,f
相反的方向,则f(x)
会最大程度地减小。 这是最陡下降方向:-∇f
或最陡梯度下降的方向。 我们在下图中对此进行说明:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yn2Q9R4m-1681567233317)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/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
开始:
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 次迭代中的变化方式:
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]
有时,我们需要优化其输入和输出为向量的函数。 因此,对于输出向量的每个分量,我们需要计算梯度向量。 对于f: R^n -> R^m
,我们将有m
个梯度向量。 通过将它们排列成矩阵形式,我们得到n x m
个偏导数J[ij] = ∂f(x)[i]/∂x[j]
的矩阵,称为 Jacobian 矩阵。
对于单个变量的实值函数,如果要在某个点测量函数曲线的曲率,则需要计算在更改输入时导数将如何变化。 这称为二阶导数。 二阶导数为零的函数没有曲率,并且是一条平线。 现在,对于几个变量的函数,有许多二阶导数。 这些导数可以布置在称为 Hessian 矩阵的矩阵中。 由于二阶偏导数是对称的,即:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wqZ6kste-1681567233318)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/3657cb60-4e28-4e83-bd5b-324637f867ad.png)]
Hessian 矩阵是实对称的,因此具有实特征值。 相应的特征向量代表不同的曲率方向。 最大和最小特征值的大小之比称为黑森州的条件数。 它测量沿每个本征维的曲率彼此相差多少。 当 Hessian 条件数较差时,梯度下降的效果较差。 这是因为,在一个方向上,导数迅速增加,而在另一个方向上,它缓慢地增加。 梯度下降并没有意识到这一变化,因此,可能需要很长时间才能收敛。
对于我们的温度示例,Hessian 为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AfBm15u5-1681567233318)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/96c6867c-f6a9-4daa-bb4b-aa31e80cf6a9.png)]
最大曲率的方向是最小曲率的方向的两倍。 因此,沿着y
遍历,我们将更快地到达最小点。 从前面的热板图中所示的温度轮廓中也可以看出这一点。
我们可以使用二阶导数曲率信息来检查最佳点是最小还是最大。 对于单个变量,f'(x) = 0
,f''(x) > 0
表示x
是f
的局部最小值, 并且f'(x) = 0
,f''(x) < 0
表示x
是局部最大值。 这称为二阶导数测试(请参见下图解释曲率)。 类似地,对于几个变量的函数,如果 Hessian 在x
为正定(即所有本征值均为正),则f
会在x
达到局部最小值。 如果 Hessian 在x
处为负定值,则f
在x
处达到局部最大值。 如果 Hessian 同时具有正和负特征值,则x
是f
的鞍点。 否则,测试没有定论:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qvSMAnUT-1681567233318)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/25e288db-cbec-4a2b-a1dd-636f85fae3c7.png)]
解释曲率
存在基于使用曲率信息的二阶导数的优化算法。 牛顿法就是这样一种方法,对于凸函数,它只需一步就可以达到最佳点。
令f
和g
均为单个变量的实值函数。 假设y = g(x)
和z = f(g(x)) = f(y)
。
然后,导数的链式规则指出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OsZlZMOu-1681567233318)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/46fda9c6-12cf-4933-9a8a-14d4eb2bd6ec.png)]
同样,对于几个变量的函数,令x ∈ R^m
,y ∈ R^n
,g: R^m -> R^n
,f: R^n -> R
,y = g(x)
,z = f(y)
,然后:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UsISQtai-1681567233318)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/3e5c28bf-979d-492c-afea-53f995e924d7.png)]
因此,z
相对于x
的梯度∂z/∂x
表示为 Jacobian ∂y/∂x
与∂z/∂y
梯度向量的乘积。 因此,对于多个变量的函数,我们具有导数的链式规则,如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EPVdu5Fs-1681567233319)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/03d7d7d4-46f1-4cf2-a857-d315b9503cef.png)]
神经网络学习算法由几个这样的雅可比梯度乘法组成。
几乎所有的神经网络学习都由一种非常重要的算法提供支持:SGD。 这是常规梯度下降算法的扩展。 在 ML 中,损失函数通常写为样本损失函数之和,作为自助餐厅示例中的平方误差E
。 因此,如果我们有m
个训练示例,则梯度函数也将具有m
个可加项。
梯度的计算成本随着m
线性增加。 对于十亿大小的训练集,前面的梯度计算将花费很长时间,并且梯度下降算法将朝着收敛的方向非常缓慢地进行,从而在实践中无法进行学习。
SGD 取决于对梯度实际上是期望值的简单理解。 我们可以通过在小样本集上计算期望值来近似。 可以从训练集中随机抽取m'
(比m
小得多的小批量)样本大小,并且梯度可以近似为计算单个梯度下降步骤。 让我们再次考虑自助餐厅示例。 应用链式规则,误差函数(三个变量的函数)的梯度由下式给出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dIxn0LnN-1681567233319)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/95f79b2d-3b5e-4219-bc97-62174d8ce7d2.png)]
现在,代替使用所有 n 训练示例来计算导数,如果我们从训练示例中抽取少量随机样本,我们仍然可以合理地近似导数。
E
的梯度给出了权重更新的估计值。 我们可以通过将其乘以一个常数ε
(称为学习率)来进一步控制它。 取得非常高的学习率可能会增加而不是使优化目标函数值最小化。
在 SGD 中,在将每个小批量展示给算法后,将更新权重。 将整个训练数据一次呈现给训练算法需要很多数据点/批量大小的步骤。 一个周期描述了算法看到整个数据集的次数。
以下是自助餐厅问题的keras
代码。 假设鱼类的实际价格为 150 美分,薯条为 50 美分,番茄酱为 100 美分。 我们已随机生成餐中物品的样本部分。 假设初始的价格为每份 50 美分。 30 个周期后,我们得到的估计值与商品的真实价格非常接近:
#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-53z02R0W-1681567233319)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/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 单元。 这是一个阶跃函数,不可微分为零。 同样,在非零点,其导数为零。 其他常用的激活函数是 Sigmoid,tanh 和 ReLu。 下图提供了这些函数的定义和图解:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D7waq4Ts-1681567233319)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/965ff81d-4930-4829-ab82-679911178735.png)]
激活函数图
这是激活函数定义:
函数名称 | 定义 |
---|---|
二元阈值 | [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C2BsxmqZ-1681567233320)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/c1d4835d-3647-4413-9da0-8e0b5ab7d2e0.png)] |
Sigmoid | [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R8UXMMDi-1681567233320)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/721c6c66-6a07-4a20-a6c0-b8db4789461e.png)] |
tanh | [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XQOOb6x5-1681567233320)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/d6ae84fd-03b3-4511-9ce7-cf04ed45a6c9.png)] |
ReLU | [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bE2hkIB6-1681567233320)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/af2bf69a-947a-4c6e-a8f5-09769d6c213b.png)] |
或 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qVqhlwrL-1681567233320)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/7137e27c-ad6f-4d2b-a734-7add27c6a166.png)] |
如果我们有一个 K 类(K > 2
)分类问题,那么我们基本上想学习条件概率分布P(y | x)
。 因此,输出层应具有 K 个神经元,其值应为 1。为了使网络了解所有 K 单元的输出应为 1, 使用 softmax 激活函数。 这是 Sigmoid 激活的概括。 像 Sigmoid 函数一样,softmax 函数将每个单元的输出压缩为 0 到 1 之间。
而且,它会将每个输出相除,以使输出的总和等于 1:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-juV8Q2Mo-1681567233321)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/099f52bb-312d-4d9b-a510-8824481123e2.png)]
数学上,softmax 函数如下所示,其中z
是输出层输入的向量(如果有 10 个输出单元,则z
中有 10 个元素)。 同样,j
索引输出单元,因此j = 1, 2, ..., K
:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9G03nSSn-1681567233321)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/c6d124bb-1721-4458-82e8-4d805f6440b6.png)]
假设我们有两个类分类问题; 也就是说,我们需要预测二元结果变量y
的值。 就概率而言,结果y
取决于特征x
的伯努利分布。 神经网络需要预测概率P(y = 1 | x)
。 为了使神经网络的输出成为有效概率,它应该位于[0, 1]
中。 为此,我们使用 Sigmoid 激活函数并获得非线性逻辑单元。
要学习逻辑单元的权重,首先我们需要一个成本函数并找到成本函数的导数。 从概率的角度来看,如果我们想最大化输入数据的可能性,则交叉熵损失会作为自然成本函数出现。 假设我们有一个训练数据集X = {x[n], t[n]}
,n = 1, …, N
,似然函数可以写成:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4qwyJfeL-1681567233321)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/9b17fcb0-2380-4a14-a86a-bf3bf235f078.png)]
其中y[n]
是在将x[n]
作为输入数据传递到逻辑单元之后,Sigmoid 单元的输出。 注意t
和w
分别表示 Sigmoid 单元的目标向量(训练集中的所有N
个目标值)和权重向量(所有权重的集合)。 可以通过采用似然性的负算法来定义误差函数,这给出了交叉熵代价函数:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-krI4KNaf-1681567233321)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/504e31b7-bbd1-4ae6-ae1c-a09536802838.png)]
要学习逻辑神经单元的权重,我们需要关于每个权重的输出导数。 我们将使用导数的链式规则来导出逻辑单元的误差导数:
让:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vW8tJKzH-1681567233321)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/7628813a-e498-4e7a-9eaa-160d5169eebb.png)]
因此:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fmU9gtPs-1681567233322)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/f774189e-1543-4caf-b12e-a89dcb004d66.png)]
就线性单元的平方误差损失而言,我们发现的导数看起来与导数非常相似,但它们并不相同。 让我们仔细看一下交叉熵损失,看看它与平方误差有何不同。 我们可以如下重写交叉熵损失:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JvCKentw-1681567233322)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/d7ee5e5b-72b4-4115-a7e2-f5f0834b7053.png)]
t[n] = 1
,y[n] = 1 => E(w) = 0
,但y[n] = 0 => E(w) = +∞
t[n] = 0
,y[n] = 1 => E(w) = +∞
,但y[n] = 0 => E(w) = 0
现在,让我们尝试将平方误差损失与逻辑输出一起使用。 因此,我们的成本函数为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-frPF8Svx-1681567233322)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/67da829c-5832-4cb4-8d09-fe4617f87e1c.png)]
因此:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2nkkyKwo-1681567233322)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/780a5531-7c6f-4872-b6fd-dba4f22ac05f.png)]
该误差导数直接取决于 Sigmoid 函数σ'(y[n])
的导数。 现在,当y[n]
高度负值时,Sigmoid 函数趋于 0;当y[n]
高度正值时,Sigmoid 函数趋于 1。 从 Sigmoid 曲线的平坦水平区域可以明显看出,对于y[n]
的这种值,梯度可以缩小得太小。 因此,即使t[n]
和y[n]
不一致,对于这些数据点,平方误差导数也将具有很小的更新。 也就是说,它们被网络严重错误分类。 这称为消失梯度问题。 因此,基于最大似然的交叉熵损失几乎始终是训练逻辑单元的首选损失函数。
损失函数将神经网络的输出与训练中的目标值进行比较,产生一个损失值/分数,以测量网络的预测与期望值的匹配程度。 在上一节中,我们看到了针对不同任务(例如回归和二分类)的不同类型损失函数的需求。 以下是一些其他流行的损失函数:
神经网络训练的训练集中的所有输入和目标都必须表示为张量(或多维数组)。 张量实际上是将二维矩阵推广到任意数量的维。 通常,这些是浮点张量或整数张量。 无论原始输入数据类型是什么(图像,声音,文本),都应首先将其转换为合适的张量表示形式。 此步骤称为数据向量化。 以下是本书中经常使用的不同维度的张量:
通过将三维张量放置在一个数组中,可以创建三维张量。 等等。 在深度学习中,通常我们使用零维到四维张量。
张量具有三个关键属性:
以下是在讨论迁移学习用例时将经常使用的一些示例张量。
(24, 2)
的 2D 张量表示。 因此,数据批量将由 3D 张量表示。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dZgRy1JN-1681567233322)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/e567faa7-d096-4c23-a921-00b647dced4f.png)]
可以通过一组张量运算来表述用于训练/测试深度神经网络的所有计算。 例如,张量的相加,相乘和相减。 以下是本书中一些常用的张量运算:
+
,-
和*
)按元素进行对相同形状的两个张量进行运算。(x, y)
,我们应具有x.shape[1] = y.shape[0]
。output = relu(dot(W, input) + b)
。 在这里,我们正在计算权重矩阵与输入向量x
的点积。 这将产生一个向量,然后添加一个标量偏差项。 实际上,我们希望将偏置项添加到点积输出向量的每个元素中。 但是,偏置张量是零维张量,向量是一维。 因此,这里我们需要广播较小的张量以匹配较大张量的形状。 广播涉及两个步骤:将轴添加到较小的张量以匹配较大张量的尺寸。 然后,重复较小的张量以匹配较大张量的形状。 我们将通过一个具体示例对此进行说明:令x
为形状(32, 10)
,y
为形状(10)
。 我们要计算x + y
。 在广播的第一步之后,我们将轴添加到y
(较小的张量),并得到形状为(1, 10)
的张量y1
。 为了匹配x
的尺寸,我们将y1
重复 32 次,并得到形状为(32, 10)
的y2
张量。 然后,我们计算元素加法x + y2
。#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),并尝试使用线性单元。 通过分析求解,得出所需权重:w = (0, 0)
和偏差:b = 1/2
。 该模型为所有输入值输出 0.5。 因此,简单的线性神经元无法学习 XOR 函数。
解决 XOR 问题的一种方法是使用输入的不同表示形式,以便线性模型能够找到解决方案。 这可以通过向网络添加非线性隐藏层来实现。 我们将使用带有两个隐藏单元的 ReLU 层。 输出是布尔值,因此最适合的输出神经元是逻辑单元。 我们可以使用二元交叉熵损失来学习权重:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HIaHqEyN-1681567233323)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/7c2d473b-3c3c-4d00-ab64-c2a35fe6dfc3.png)]
让我们使用 SGD 学习此网络的权重。 以下是 XOR 函数学习问题的keras
代码:
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-kDvOgtzq-1681567233323)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/b78cd483-fea2-4c10-81ef-85f419250bd9.png)]
(左)显示 4 点的原始空间-显然没有行可以将 0 类{(0, 0), (1, 1)}
与其他类分开。 (中心)显示隐藏的 ReLU 层学习到的变换空间。 (右)该表显示了通过该函数获得的预测值
具有一层隐藏层的神经网络能够学习 XOR 函数。 这个例子说明了神经网络需要非线性隐藏层来做有意义的事情。 让我们仔细看一下隐藏层学习了哪些输入转换,从而使输出逻辑神经元学习该函数。 在 Keras 中,我们可以从学习的模型中提取中间隐藏层,并使用它来提取传递给输出层之前输入的转换。 上图显示了如何转换四个点的输入空间。 转换后,可以用一条线轻松地分隔 1 类和 0 类点。 这是为原始空间和变换后的空间生成图的代码:
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
#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 发表了一篇论文,标题为《通过反向传播的误差学习算法》,该算法的重要性已为大型 ML 社区所认可。 反向传播是最早能够证明人工神经网络可以学习良好内部表示的方法之一。 也就是说,它们的隐藏层学习了非平凡的特征。
反向传播算法是在单个训练示例上针对每个权重计算误差导数dE/dθ
的有效方法。 为了理解反向传播算法,让我们首先代表一个带有计算图符号的神经网络。 神经网络的计算图将具有节点和有向边,其中节点代表变量(张量),边代表连接到下一个变量的变量的运算。 如果y = f(x)
,则变量x
通过有向边连接到y
,对于某些函数f
。
例如,逻辑单元的图形可以表示如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GbAVlkh9-1681567233323)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/4654a29a-6aef-4c89-8ad1-e7dc98dbb64f.png)]
(左)逻辑回归作为计算图。 (右)三层网络计算图的 BP 算法信息流
我们用u[1], u[2], ..., u[n]
表示计算节点。 另外,我们按顺序排列节点,以便可以一个接一个地计算它们。 u[n]
是一个标量-损失函数。 让我们用节点θ[k]
表示网络的参数或权重。 要应用梯度下降,我们需要计算所有导数∂u^n/∂θ[k]
。 可以通过遵循从输入节点到最终节点∂u^n
的计算图中的有向路径来计算该图的正向计算。 这称为前向传播。
由于图中的节点为张量,因此要计算偏导数∂u^n/∂θ[k]
,将使用多个变量函数的导数链式规则,该规则由雅可比矩阵与梯度的乘积表示。 反向传播算法涉及一系列这样的雅可比梯度积。
反向传播算法表示如下:
X = {x[n]}
,目标向量Y = {t[n]}
,用于测量网络误差的成本函数C
以及网络的初始权重集,以计算网络的前向通过并计算损耗C
(x[n], t[n])
,针对每个层参数/权重计算损耗的导数C
-此步骤讨论如下:
Δθ[l] = -α · ∂C/∂θ[l]
,α
为学习率我们将使用完全连接的三层神经网络解释反向传播。 上图显示了为此的计算图。 令z(i)
表示图中的计算节点。 为了执行反向传播,导数∂C/∂z(i)
的计算将与正向传递的反向顺序完全相同。 这些由向下箭头指示。 让我们表示关于层l
的输入z(l)
的成本函数的导数δ(l)
。 对于最顶层,让δ(4) = 1
。 为了递归计算,让我们考虑一个单层。 一层具有输入z(l)
和输出z(l+1)
。 同样,该层将接受输入δ(l + 1)
并产生δ(l)
和∂C/∂θ[l]
。
对于层l
:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Np4Bb0TP-1681567233323)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/05f06392-f5cc-44ff-8755-ebeded169ff2.png)]
i
代表梯度δ(l)[i]
的第i
个分量。
因此,我们得出了用于计算反向消息的递归公式。 使用这些,我们还可以计算关于模型参数的成本导数,如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9peOzrJw-1681567233324)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/05544f28-196c-4245-83df-4fcf348f236d.png)]
反向传播算法在计算图中相对于其祖先x
,计算标量成本函数z
的梯度。 该算法开始于计算关于其本身的成本z
的导数∂z/∂z = 1
。 可以通过将当前梯度乘以产生z
的运算的雅可比行列式来计算关于z
父级的梯度。 我们一直向后遍历计算图并乘以雅可比行列式,直到达到输入x
为止。
通常,优化是一项非常困难的任务。 在本节中,我们讨论了用于训练深度模型的优化方法所涉及的一些常见挑战。 了解这些挑战对于评估神经网络模型的训练表现并采取纠正措施以缓解问题至关重要。
矩阵的条件数是最大奇异值与最小奇异值之比。 如果条件数非常高,则矩阵是病态的,通常表示最低的奇异值比最高的奇异值小几个数量级,并且矩阵的行彼此高度相关。 这是优化中非常普遍的问题。 实际上,这甚至使凸优化问题也难以解决。 通常,神经网络会出现此问题,这会导致 SGD 卡住,即,尽管存在很强的梯度,学习也会变得非常缓慢。 对于具有良好条件数(接近 1)的数据集,误差轮廓几乎是圆形的,并且负梯度始终笔直指向误差表面的最小值。 对于条件差的数据集,误差表面在一个或多个方向上相对平坦,而在其他方向上则强烈弯曲。 对于复杂的神经网络,可能无法通过解析找到 Hessian 和病态效应。 但是,可以通过在训练周期内绘制平方梯度范数和g^T H[g]
来绘制图表,以监控疾病的影响。
让我们考虑我们要优化的f(x)
函数的二阶泰勒级数逼近。z[0]
点的泰勒级数由下式给出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VR9l7kff-1681567233324)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/28d6a247-eb12-45c2-a471-8673b8610e63.png)]
其中g
是梯度向量, H
是f(x)
在x[0]
时的 Hessian。 如果ε
是我们使用的学习率,则根据梯度下降的新点为x[0] - ε[g]
。 将其替换为 Taylor 系列展开式,我们得到:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iigbiBQp-1681567233324)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/88f6f0bb-b7d9-4a33-8d96-f46e8db8138b.png)]
注意,如果-ε g^t g + ½ε^2 g^T H[g] > 0
,则与x[0]
相比,新点的函数值会增加。 同样,在存在强梯度的情况下,我们将具有较高的平方梯度范数||g||^2 = g^T g
,但同时,如果其他数量为g^T H[g]
增长一个数量级,那么我们将看到f(x)
的下降速度非常缓慢。 但是,如果此时可以缩小学习率ε
,则可能会在某种程度上使这种影响无效,因为g^T H[g]
数量乘以ε^2
。 可以通过在训练周期绘制平方梯度范数和g^T H[g]
来监测疾病的影响。 我们在热板中看到了如何计算梯度范数的示例。
DNN 模型实质上可以保证具有极大数量的局部最小值。 如果局部最小值与全局最小值相比成本较高,则可能会出现问题。 长期以来,人们一直认为,由于存在这样的局部极小值,神经网络训练受到了困扰。 这仍然是一个活跃的研究领域,但是现在怀疑对于 DNN,大多数局部最小值具有较低的成本值,没有必要找到全局最小值,而是在权重空间中具有足够低的成本函数值。 可以通过监视梯度范数来检测强局部极小值的存在。 如果梯度范数减小到很小的数量级,则表明存在局部极小值。
鞍点是既不是最大值也不是最小值的点,而是被平坦区域围绕,该平坦区域的一侧目标函数值增大,而另一侧目标函数减小。 由于该平坦区域,梯度变得非常小。 然而,已经观察到,凭经验梯度下降迅速逃离了这些区域。
高度非线性 DNN 的目标函数具有非常陡峭的区域,类似于悬崖,如下图所示。 在极陡峭的悬崖结构的负梯度方向上移动会使权重移得太远,以致我们完全跳下悬崖结构。 因此,在我们非常接近的时候错过了极小值。
因此,取消了为达到当前解决方案所做的许多工作:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fPZ8r2pA-1681567233324)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/7da599f5-2250-4acf-b6ef-e8dd7d188957.png)]
解释何时需要裁剪梯度范数
我们可以通过裁剪梯度来避免梯度下降中的此类不良动作,也就是说,设置梯度幅度的上限。 我们记得梯度下降是基于函数的一阶泰勒近似。 这种近似在计算梯度的点附近的无穷小区域中保持良好。 如果我们跳出该区域,成本函数可能开始增加或向上弯曲。 因此,我们需要限制移动的时间。 梯度仍然可以给出大致正确的方向。 必须将更新选择为足够小,以避免越过向上的弯曲。 一种实现此目的的方法是通过设置标准的上限阈值来限制梯度边界:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WWrj8RVe-1681567233325)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/2fbcbddc-c681-475e-a924-7491c6bcf2a8.png)]
在 Keras 中,可以如下实现:
#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-bvsHcfyF-1681567233325)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/a59194e9-3b84-4be6-b6d2-f1d2fa8c7f01.png)]
解释初始化误差的情况以及对基于梯度的优化的影响
大多数优化算法都是基于这样的假设,即我们在给定点具有已知的精确梯度。 但是,实际上我们只对梯度有一个估计。 这个估计有多好? 在 SGD 中,批次大小会极大地影响随机优化算法的行为,因为它确定了梯度估计的方差。
总之,可以通过以下四个技巧来解决神经网络训练中面临的不同问题:
现在,让我们简要讨论一下各种启发式方法/策略,这些方法使学习 DNN 切实可行并继续使深度学习取得巨大成功。
以下是初始点的选择如何影响深度神经网络的迭代学习算法的表现:
初始化算法主要是启发式的。 良好初始化的全部要点是可以以某种方式使学习更快。 初始化的重要方面之一是破坏初始权重集对隐藏层单元的对称性。 如果以相同的权重对其进行初始化,则在网络相同级别上具有相同激活函数的两个单元将被同等更新。 多个单元保留在隐藏层中的原因是它们应该学习不同的特征。 因此,获得同等更新不会影响其他特征的学习。
打破对称性的一种简单方法是使用随机初始化-从高斯或均匀分布中采样。 模型中的偏差参数可以通过启发式选择常量。 选择权重的大小取决于优化和正则化之间的权衡。 正则化要求权重不应太大-这可能导致不良的泛化表现。 优化需要权重足够大,才能成功地通过网络传播信息。
让我们考虑具有m
输入和n
输出单元的密集层:
[-1/√m, 1/√m]
中采样每个权重。
√(2/(m+n))
的正态分布中采样每个权重。 这类似于 Glorot Uniform,称为 Glorot Normal。
初始化方案也可以视为神经网络训练中的超参数。 如果我们有足够的计算资源,则可以评估不同的初始化方案,我们可以选择具有最佳泛化表现和更快的收敛速度的方案。
近年来,已提出了不同的优化算法,这些算法使用不同的方程式更新模型的参数。
成本函数可能具有高曲率和较小但一致的梯度的区域。 这是由于 Hessian 矩阵的条件不佳以及随机梯度的方差。 SGD 在这些地区可能会放慢很多速度。 动量算法会累积先前梯度的指数加权移动平均值(EWMA),并朝该方向移动,而不是 SGD 建议的局部梯度方向。 指数加权由超参数α ∈ [0, 1)
控制,该超参数确定先前梯度的影响衰减的速度。 动量法通过组合相反符号的梯度来阻尼高曲率方向上的振荡。
Nesterov 动量是动量算法的一种变体,仅在计算梯度时与动量方法不同。 标准动量法首先在当前位置计算梯度,然后在累积梯度的方向上发生较大的跳跃。 涅斯特罗夫动量首先沿先前累积的梯度的方向跃升,然后计算新点的梯度。 通过再次采用所有先前梯度的 EWMA 来校正新梯度:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hpx0UDKq-1681567233325)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/1cb15e3c-28b7-46d1-9475-859ca97ba73b.png)]
在前面的方法中,将相同的学习率应用于所有参数更新。 由于数据稀疏,我们可能想在不同程度上更新参数。 诸如 AdaGrad,AdaDelta,RMSprop 和 Adam 之类的自适应梯度下降算法通过保持每个参数的学习率,提供了经典 SGD 的替代方法。
AdaGrad 算法通过按与先前所有梯度的平方和值的平方根成比例的方式将它们成反比例缩放来调整每个连接的学习率。 因此,在误差表面的平缓倾斜方向上进行了较大的移动。 但是,从一开始就采用这种技巧可能会导致某些学习率急剧下降。 但是,AdaGrad 在一些深度学习任务上仍然表现出色。
RMSprop 通过采用先前平方梯度的 EWMA 来修改 AdaGrad 算法。 它具有移动平均参数ρ
,它控制移动平均的长度和比例。 这是深度神经网络训练最成功的算法之一。
自适应力矩(Adam)。 它充分利用了基于动量的算法和自适应学习率算法,并将它们组合在一起。 在此,动量算法应用于由 RMSprop 计算的重新缩放的梯度。
与其他任何 ML 训练一样,用于训练深度学习模型的数据集也分为训练,测试和验证。 在模型的迭代训练期间,通常,验证误差比训练误差略大。 如果测试误差和验证误差之间的差距随着迭代的增加而增加,则是过拟合的情况。 如果训练误差不再减小到足够低的值,我们可以得出结论,该模型是欠拟合的。
模型的能力描述了模型可以建模的输入输出关系的复杂性。 也就是说,在模型的假设空间中允许有多大的函数集。 例如,可以将线性回归模型推广为包括多项式,而不只是线性函数。 这可以通过在构建模型时将 x 的 n 积分乘以 x 作为积分来完成。 还可以通过向网络添加多个隐藏的非线性层来控制模型的容量。 因此,我们可以使神经网络模型更宽或更深,或两者同时进行,以增加模型的容量。
但是,在模型容量和模型的泛化误差之间需要权衡:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6h0FqmLD-1681567233325)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/1fa01356-977d-41e1-ab20-36fab28171bf.png)]
(左):线性函数根据数据拟合而拟合。 (中):适合数据的二次函数可以很好地推广到看不见的点 (右)适合数据的次数为 9 的多项式存在过拟合的问题
具有极高容量的模型可能通过训练集中的学习模式而过拟合训练集,而训练模式可能无法很好地推广到看不见的测试集。 而且,它非常适合少量的训练数据。 另一方面,低容量的模型可能难以适应训练集:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w5SKZYSo-1681567233326)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/2ade6f4c-448f-4fad-acd7-2ef73fac4d5e.png)]
在训练和验证损失方面过拟合/欠拟合
过拟合是 ML 中的核心问题。 对于神经网络,开发了许多策略来避免过拟合并减少泛化误差。 这些策略统称为正则化。
权重共享意味着同一组权重在网络的不同层中使用,因此我们需要优化的参数更少。 在一些流行的深度学习架构中可以看到这一点,例如暹罗网络和 RNN。 在几层中使用共享权重可以通过控制模型容量来更好地推广模型。 反向传播可以轻松合并线性权重约束,例如权重共享。 CNN 中使用了另一种权重分配方式,其中与完全连接的隐藏层不同,卷积层在局部区域之间具有连接。 在 CNN 中,假设可以将要由网络处理的输入(例如图像或文本)分解为具有相同性质的一组局部区域,因此可以使用相同的一组转换来处理它们。 是,共享权重。 RNN 可以被视为前馈网络,其中每个连续的层共享相同的权重集。
可以看到,像前面示例中的多项式一样,过拟合模型的权重非常大。 为了避免这种情况,可以将罚分项Ω
添加到目标函数中,这将使权重更接近原点。 因此,惩罚项应该是权重范数的函数。 同样,可以通过乘以超参数α
来控制惩罚项的效果。 因此我们的目标函数变为:E(w) + αΩ(w)
。 常用的惩罚条款是:
Ω = 1/2 · ||w||^2
给出。 在回归文献中,这称为岭回归。Ω = ||w||[1] = Σ[i](w[i])
给出。 这称为 LASSO 回归。L1 正则化导致稀疏解; 也就是说,它会将许多权重设置为零,因此可以作为回归问题的良好特征选择方法。
随着对大型神经网络的训练的进行,训练误差会随着时间的推移而稳步减少,但如下图所示,验证集误差开始增加,超出了某些迭代:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U6hWAtrE-1681567233326)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/14c4482c-cab1-4fa7-841f-f1130804fa87.png)]
提前停止:训练与验证误差
如果在验证误差开始增加的时候停止训练,我们可以建立一个具有更好泛化表现的模型。 这称为提前停止。 它由耐心超参数控制,该参数设置了中止训练之前观察增加的验证集误差的次数。 提前停止可以单独使用,也可以与其他正则化策略结合使用。
丢弃法是一种在深度神经网络中进行正则化的计算廉价但功能强大的方法。 它可以分别应用于输入层和隐藏层。 通过在正向传递过程中将节点的输出设置为零,丢弃法随机掩盖了一部分节点的输出。 这等效于从层中删除一部分节点,并创建一个具有更少节点的新神经网络。 通常,在输入层上会删除 0.2 个节点,而在隐藏层中最多会删除 0.5 个节点。
模型平均(集成方法)在 ML 中被大量使用,通过组合各种模型的输出来减少泛化误差。 套袋是一种整体方法,其中通过从训练集中替换并随机抽样来构建 k 不同的数据集,并在每个模型上训练单独的 k 模型。 特别地,对于回归问题,模型的最终输出是 k 模型的输出的平均值。 还有其他组合策略。
还可以将丢弃视为一种模型平均方法,其中通过更改应用了丢弃的基本模型的各个层上的活动节点数来创建许多模型。
在 ML 中,通常的做法是先缩放并标准化输入的训练数据,然后再将其输入模型进行训练。 对于神经网络而言,缩放也是预处理步骤之一,并且已显示出模型表现的一些改进。 在将数据馈送到隐藏层之前,我们可以应用相同的技巧吗? 批量规范化基于此思想。 它通过减去激活的最小批量平均值μ
并除以最小批量标准差σ
来归一化前一层的激活。 在进行预测时,我们一次可能只有一个示例。 因此,不可能计算批次均值μ
和批次σ
。 将这些值替换为训练时收集的所有值的平均值。
使神经模型具有更好的概括性或测试表现的最佳方法是通过训练它获得更多数据。 实际上,我们的训练数据非常有限。 以下是一些用于获取更多训练数据的流行策略:
神经网络的架构级参数,例如隐藏层数,每个隐藏层的单元数,以及与训练相关的参数,例如学习率,优化器算法,优化器参数-动量,L1/L2 正则化器和丢弃法统称为神经网络的超参数。 神经网络的权重称为神经网络的参数。 一些超参数影响训练算法的时间和成本,而一些影响模型的泛化表现。
开发了多种用于超参数调整的方法。 但是,对于大多数参数,需要为每个超参数指定一个特定范围的值。 可以通过了解它们对模型容量的影响来设置大多数超参数。
网格搜索是对超参数空间的手动指定子集的详尽搜索。 网格搜索算法需要表现指标,例如交叉验证误差或验证集误差,以评估最佳可能参数。 通常,网格搜索涉及选择对数刻度的参数。 例如,可以从集合{50, 100, 200, 500, 1000,...}
中选择在集合{0.1, 0.01, 0.001, 0.0001}
内获得的学习率或多个隐藏单元。 网格搜索的计算成本随着超参数的数量呈指数增长。 因此,另一种流行的技术是随机网格搜索。 随机搜索从所有指定的参数范围中对参数采样固定次数。 当我们具有高维超参数空间时,发现这比穷举搜索更有效。 更好,因为可能存在一些不会显着影响损耗的超参数。
在本章中,我们涉及了深度学习的基础知识。 我们真的赞扬您为实现这一目标所做的努力! 本章的目的是向您介绍与深度学习领域有关的核心概念和术语。 我们首先简要介绍了深度学习,然后介绍了当今深度学习领域中流行的框架。 还包括详细的分步指南,用于设置您自己的深度学习环境,以在 GPU 上开发和训练大规模深度学习模型。
最后,我们涵盖了围绕神经网络的基本概念,包括线性和非线性神经元,数据表示,链式规则,损失函数,多层网络和 SGD。 还讨论了神经网络中的学习挑战,包括围绕局部极小值和梯度爆炸的常见警告。 我们研究了神经网络中过拟合和欠拟合的问题,以及处理这些问题的策略。 然后,我们介绍了神经网络单元的流行初始化启发法。 除此之外,我们还探索了一些更新的优化技术,它们是对香草 SGD 的改进,其中包括 RMSprop 和 Adam 之类的流行方法。
在下一章中,我们将探讨深度学习模型周围的各种架构,这些架构可用于解决不同类型的问题。
本章将着重于理解当今深度学习中存在的各种架构。 神经网络的许多成功都在于对神经网络架构的精心设计。 自 1960 年代的传统人工神经网络(ANNs)以来,我们已经走了很长一段路。 在本书中,我们介绍了基本模型架构,例如完全连接的深度神经网络,卷积神经网络(CNN),循环神经网络(RNN),长短期记忆(LSTM)网络,以及最新的胶囊网络。
在本章中,我们将介绍以下主题:
架构一词是指神经网络的整体结构,包括其可以具有多少层以及各层中的单元应如何相互连接(例如,连续层中的单元可以完全连接) ,部分连接,或者甚至可以完全跳过下一层,然后再连接到网络中更高级别的一层。 随着模块化深度学习框架(例如 Caffe,Torch 和 TensorFlow)的出现,复杂的神经网络设计发生了革命性的变化。 现在,我们可以将神经网络设计与 Lego 块进行比较,在这里您可以构建几乎可以想象的任何结构。 但是,这些设计不仅仅是随机的猜测。 这些设计背后的直觉通常是由设计人员对问题的领域知识以及一些反复试验来精调最终设计的结果所驱动。
前馈多层神经网络具有学习巨大的假设空间并提取每个非线性隐藏层中复杂特征的能力。 那么,为什么我们需要不同的架构? 让我们尝试理解这一点。
特征工程是机器学习(ML)中最重要的方面之一。 如果特征太少或不相关,则可能会导致拟合不足。 而且特征太多,可能会使数据过拟合。 创建一组好的手工制作的特征是一项繁琐,耗时且重复的任务。
深度学习带有一个希望,即给定足够的数据,深度学习模型能够自动确定正确的特征集,即复杂性不断增加的特征层次。 好吧,深度学习的希望是真实的,并且会产生误导。 深度学习确实在许多情况下简化了特征工程,但是它并没有完全消除对它的需求。 随着手动特征工程的减少,神经网络模型本身的架构变得越来越复杂。 特定架构旨在解决特定问题。 与手工特征工程相比,架构工程是一种更为通用的方法。 在架构工程中,与特征工程不同,领域知识不会硬编码为特定特征,而只会在抽象级别使用。 例如,如果我们要处理图像数据,则有关该数据的一个非常高级的信息是对象像素的二维局部性,而另一个是平移不变性。 换句话说,将猫的图像平移几个像素仍然可以保持猫的状态。
在特征工程方法中,我们必须使用非常具体的特征(例如边缘检测器,拐角检测器和各种平滑滤波器)来为任何图像处理/计算机视觉任务构建分类器。 现在,对于神经网络,我们如何编码二维局部性和翻译不变性信息? 如果将密集的完全连接层放置在输入数据层之前,则图像中的每个像素都将连接到密集层中的每个单元。 但是,来自两个空间遥远对象的像素不必连接到同一隐藏单元。 经过长时间的大量数据训练后,具有很强的 L1 正则化能力的神经网络可能能够稀疏权重。 我们可以设计架构以仅将本地连接限制到下一层。 少量的相邻像素(例如,像素的10 x 10
子图像)可能具有与隐藏层的一个单元的连接。 由于转换不变,因此可以重用这些连接中使用的权重。 CNN 就是这样做的。 这种权重重用策略还有其他好处,例如大大减少了模型参数的数量。 这有助于模型进行概括。
让我们再举一个例子,说明如何将抽象领域知识硬编码到神经网络中。 假设我们有时间数据或顺序数据。 正常的前馈网络会将每个输入示例视为独立于先前的输入。 因此,学习到的任何隐藏特征表示也应取决于数据的最近历史,而不仅仅是当前数据。 因此,神经网络应该具有一些反馈回路或记忆。 这个关键思想产生了循环神经网络架构及其现代的强大变体,例如 LSTM 网络。
其他高级 ML 问题(例如语音翻译,问题解答系统和关系建模)要求开发各种深度学习架构。
现在让我们看一些流行的神经网络架构及其应用。 我们将从多层感知器(MLP)网络开始。 我们已经介绍了单层感知器网络,这是最基本的神经网络架构。
MLP 或简单的深层神经网络(DNNs)是神经网络架构的最基本形式。 神经单元一层又一层地排列,相邻的网络层彼此完全连接。 我们已经在上一章中对此进行了详细讨论:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oenQf7nx-1681567233326)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/12eeeba8-9883-4473-a839-66a9d7304c32.png)]
自编码器通常用于减少神经网络中数据的维数。 自编码器也已成功用于异常检测和新颖性检测问题。 自编码器神经网络属于无监督学习类别。 在此,目标值设置为等于输入值。 换句话说,我们想学习单位特征。 通过这样做,我们可以获得数据的紧凑表示。
通过最小化输入和输出之间的差异来训练网络。 典型的自编码器架构是 DNN 架构的略微变体,其中,每个隐藏层的单元数量逐渐减少,直到某个点,然后逐渐增加,最终层尺寸等于输入尺寸。 其背后的关键思想是在网络中引入瓶颈,并迫使其学习有意义的紧凑表示形式。 隐藏单元的中间层(瓶颈)基本上是输入的降维编码。 隐藏层的前半部分称为编码器,后半部分称为解码器。 下面描述了一个简单的自编码器架构。 名为z
的层是此处的表示层:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EkdSmdTn-1681567233326)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/39e61d86-4db6-460c-a13b-8da4dbcd4889.png)]
深度很深的自编码器很难训练,并且容易过度安装。 有许多改进了自编码器训练方式的开发,例如使用受限玻尔兹曼机(RBM)进行生成式预训练。 变分自编码器(VAE)也是生成模型,与其他深层生成模型相比,VAE 在计算上易于处理且稳定,可以通过有效的反向传播算法进行估计。 它们受到贝叶斯分析中变分推理的启发。
变分推理的概念如下:给定输入分布x
时,输出y
上的后验概率分布太复杂而无法使用。 因此,让我们用一个更简单的分布q(y)
来近似复杂的后验p(y|x)
。 在这里, q
是从最接近后验的分布族Q
中选择的。 例如,此技术用于训练潜在 Dirichlet 分配(LDA)(它们对文本进行主题建模,并且是贝叶斯生成模型)。 但是,经典变分推论的一个关键局限性是需要对似然性和先验共轭才能进行优化。 VAE 引入了使用神经网络来输出条件后验的方法(Kingma 和 Welling,2013 年),从而允许使用随机梯度下降(SGD)和反向传播来优化变分推断目标。 。 该方法称为重新参数化技巧。
给定数据集X
,VAE 可以生成与样本X
类似但不一定相等的新样本。数据集X
具有连续或离散随机变量x
的N
个独立且完全相同的样本。 假设数据是通过某种随机过程生成的,涉及一个未观察到的连续随机变量z
。 在简单自编码器的此示例中,变量z
是确定性的,并且是随机变量。 数据生成是一个两步过程:
z
的值是根据先验分布生成的,ρ[θ](z)
x
的值,ρ[θ](x|z)
因此, p(x)
基本上是边缘概率,计算公式为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cxlOyV5F-1681567233327)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/b2a7d0c1-ab50-49bf-8b01-9b77ea936dda.png)]
分布的参数θ
和潜变量z
都是未知的。 在此,x
可以通过从边际p(x)
取样来生成。 反向传播无法处理网络中的随机变量z
或随机层z
。 假设先验分布p(z)
为高斯分布,我们可以利用高斯分布的位置尺度属性,并将随机层重写为z =μ + σε
,其中μ
是位置参数,σ
是刻度,ε
是白噪声。 现在,我们可以获得噪声ε
的多个样本,并将它们作为确定性输入提供给神经网络。
然后,该模型成为端到端确定性深度神经网络,如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TUPf55zX-1681567233327)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/0cf59473-54cf-44b5-a5d3-36699ee1e65c.png)]
在这里,解码器部分与我们之前介绍的简单自编码器的情况相同。 训练此网络的损失函数如何? 因为这是一个概率模型,所以最直接的方法是通过边际p(x)
的最大似然来推导损失函数。 但是,该函数在计算上变得难以处理。 这样,我们将变分推理技术应用于下限L
。 导出边际似然,然后通过最大化下限L
导出损失函数。 可以在 Kingma 及其合作者的论文《自编码变分贝叶斯》(ICLR,2014 年)。 VAE 已成功应用于各个领域。 例如,文本的深层语义哈希是由 VAE 完成的,其中将文本文档转换为二进制代码。 同样,相似的文档具有相似的二进制地址。 这样,这些代码可用于更快,更有效的检索,以及文档的聚类和分类。
自从 Ian Goodfellow 及其合著者在 2014 年 NIPS 论文中首次引入以来,生成对抗网络(GAN)就广受青睐。 现在我们看到了 GAN 在各个领域的应用。 Insilico Medicine 的研究人员提出了一种使用 GAN 进行人工药物发现的方法。 他们还发现了在图像处理和视频处理问题中的应用,例如图像样式转换和深度卷积生成对抗网络(DCGAN)。
顾名思义,这是使用神经网络的另一种生成模型。 GAN 具有两个主要组成部分:生成器神经网络和判别器神经网络。 生成器网络采用随机噪声输入,并尝试生成数据样本。 判别器网络将生成的数据与真实数据进行比较,并使用 S 型输出激活来解决生成的数据是否为伪造的二分类问题。 生成器和判别器都在不断竞争,并试图互相愚弄-这就是 GAN 也被称为对抗网络的原因。 这种竞争驱使两个网络都提高其权重,直到判别器开始输出 0.5 的概率为止。 也就是说,直到生成器开始生成真实图像为止。 通过反向传播同时训练两个网络。 这是 GAN 的高级结构图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C7ROWZhx-1681567233327)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/e1a9d83e-820d-491b-8427-d1068af52a1e.png)]
训练这些网络的损失函数可以定义如下。 令p(data)
为数据的概率分布,p(g)
为生成器分布。 D(x)
表示x
来自p(data)
而非来自p(g)
的概率。 对D
进行了训练,以使将正确标签分配给G
的训练示例和样本的概率最大化。 同时,训练G
以最小化log(1 - D(G(z)))*
。 因此,D
和G
玩一个具有值函数V(D, G)
的两人 minimax 游戏:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DbCGm8iH-1681567233327)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/dc4229cc-0d2e-44a0-99a8-b6792459c1d2.png)]
可以证明,对于p(g) = p(data)
来说,这种极小极大游戏具有全局最优性。
以下是通过反向传播训练 GAN 以获得所需结果的算法:
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 的完整架构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aluSR7qU-1681567233328)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/bdfbe2ec-0f06-47ee-b805-057f3369231b.png)]
这是条件 GAN 的一种。 生成器网络获取带有噪声向量的输入文本以生成图像。 生成的图像以输入文本为条件。 使用嵌入层φ(t)
将图像描述转换为密集向量。 使用完全连接的层对其进行压缩,然后将其与噪声向量连接。 检测器网络是 CNN,并且生成器网络的架构使用具有与 CNN 网络中使用的过滤器相同的过滤器的反卷积层。 反卷积基本上是转置的卷积,我们将在后面讨论。
CNN 是专门设计用于识别形状图案的多层神经网络,这些形状图案对于二维图像数据的平移,缩放和旋转具有高度不变性。 这些网络需要以监督的方式进行训练。 通常,提供一组标记的对象类(例如 MNIST 或 ImageNet)作为训练集。 任何 CNN 模型的关键都在于卷积层和子采样/合并层。 因此,让我们详细了解在这些层中执行的操作。
CNN 背后的中心思想是卷积的数学运算,这是一种特殊的线性运算。 它广泛用于物理,统计,计算机视觉以及图像和信号处理等各个领域。 为了理解这一点,让我们从一个例子开始。 嘈杂的激光传感器正在跟踪飞船的位置。 为了更好地估计飞船的位置,我们可以取几个读数的平均值,对最近的观测结果给予更多的权重。 令x(t)
代表时间位置,t
,令w(t)
为加权函数。
该职位的估计可以写成:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jA2WYaMo-1681567233328)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/482af651-725c-47b7-916b-3e2347490314.png)]
这里,权重函数w(t)
被称为卷积的内核。 我们可以使用卷积计算位置传感器数据的简单移动平均值(SMA)。 令m
为 SMA 的窗口大小。
内核定义为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KIAc3Zhl-1681567233328)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/3ea03e05-e414-414b-a22f-a33b8cb39f64.png)]
这是使用卷积的 SMA 的 NumPy 实现:
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-OcCC1TKG-1681567233329)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/f1df58da-ec48-4fda-828a-1979a036e221.png)]
或者,也可以如下计算:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dCDUYLNK-1681567233329)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/3401841c-2937-4b3b-9e6a-d565df408d9e.png)]
下图说明了如何使用大小为 2 且步幅为 1 的内核来计算卷积层输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QS8BoYAI-1681567233329)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/11fdb71c-0ba2-48d1-a1ba-a28381d1a907.png)]
卷积内核通过一次移动一列/行来围绕输入体积进行卷积。 滤波器移位的量称为跨度。 在前面的场景中,将跨度隐式设置为 1。如果将内核跨度移动 2(两列或两行),则输出单元的数量将减少:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fDBKDNyI-1681567233329)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/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-eeFR4FXo-1681567233330)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/51b9fe99-1a68-45a1-87b6-3c24c4ce603c.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ek86gr5B-1681567233330)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/3cf0fe3d-bbc6-41cb-ac42-589a9a9d8c53.png)]
将这三个阶段结合在一起,就可以为我们提供 CNN 中的一个复杂层,这三个阶段中的每个本身就是简单层:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fNMOC0rb-1681567233330)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/b6ac6afe-c059-42a3-89cb-dbd4f5e81844.png)]
可以通过将并排堆叠在一起的方式,将合并后的特征图按一个体积进行排列,如下所示。 然后,我们可以再次对此应用下一个卷积级别。 现在,单个特征图中隐藏单元的接受场将是神经单元的体积,如下图所示。 但是,将在整个深度上使用同一组二维权重。 深度尺寸通常由通道组成。 如果我们有 RGB 输入图像,那么我们的输入本身将具有三个通道。 但是,卷积是二维应用的,并且所有通道之间的权重相同:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TO2kxZFH-1681567233331)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/d6e194c3-df63-489c-94bd-527a19d1783f.png)]
这是 LeCun 及其合作者于 1998 年设计的具有开创性的七级卷积网络,用于数字分类。 后来,它被几家银行用来识别支票上的手写数字。 网络的较低层由交替的卷积和最大池化层组成。
上层是完全连接的密集 MLP(由隐藏层和逻辑回归组成)。 第一个完全连接的层的输入是上一层的所有特征图的集合:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sOELaVTz-1681567233331)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/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 倍。
2013 年 ILSVRC 冠军是 Matthew Zeiler 和 Rob Fergus 的 CNN。 它被称为 ZFNet 。 通过调整架构的超参数,特别是通过扩展中间卷积层的大小并减小第一层的步幅和过滤器大小,它在 AlexNet 上得到了改进,从 AlexNet 的11 x 11
步幅 4 变为7 x 7
步幅。 ZFNet。 这背后的直觉是,在第一卷积层中使用较小的滤镜尺寸有助于保留大量原始像素信息。 此外,AlexNet 接受了 1500 万张图像的训练,而 ZFNet 接受了 130 万张图像的训练:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QqEpbOHO-1681567233331)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/4801ec05-89b1-43d2-ad9f-32b0d98b140e.png)]
2014 年 ILSVRC 获奖者是来自 Google 的名为 GoogLeNet 的卷积网络。 它的前 5 个错误率达到 6.67%! 这非常接近人类水平的表现。 排在第二位的是来自 Karen Simonyan 和 Andrew Zisserman 的网络,称为 VGGNet 。 GoogLeNet 使用 CNN 引入了一个称为初始层的新架构组件。 初始层背后的直觉是使用较大的卷积,但对于图像上的较小信息也要保持较高的分辨率。
因此,我们可以并行处理不同大小的内核,从1 x 1
到更大的内核,例如5 x 5
,然后将输出级联以产生下一层:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-weKfuSRs-1681567233332)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/cf82886a-ab82-4903-a258-0b84f3f27b68.png)]
显然,增加更多的层会爆炸参数空间。 为了控制这一点,使用了降维技巧。 请注意,1 x 1
卷积基本上不会减小图像的空间尺寸。 但是,我们可以使用1 x 1
滤镜减少特征图的数量,并减少卷积层的深度,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RSfshHwX-1681567233332)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/d71299bb-48ef-4a93-9098-e0aa50229f51.png)]
下图描述了完整的 GoogLeNet 架构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zBncET8k-1681567233332)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/bc08ac4f-958f-4283-b679-35c221d7292a.png)]
牛津视觉几何学组或简称为 VGG 的研究人员开发了 VGG 网络,该网络的特点是简单,仅使用3 x 3
卷积层并排叠加,且深度不断增加。 减小卷大小由最大池化处理。 最后,两个完全连接的层(每个层有 4,096 个节点)之后是 softmax 层。 对输入进行的唯一预处理是从每个像素减去在训练集上计算出的 RGB 平均值。
通过最大池化层执行池化,最大池化层跟随一些卷积层。 并非所有卷积层都跟随最大池化。 最大合并在2 x 2
像素的窗口上执行,步幅为 2。每个隐藏层都使用 ReLU 激活。 在大多数 VGG 变体中,过滤器的数量随深度的增加而增加。 下图显示了 16 层架构 VGG-16。 下一节显示了具有均匀3 x 3
x卷积(VGG-19)的 19 层架构。 VGG 模型的成功证实了深度在图像表示中的重要性:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5cq8BaAb-1681567233332)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/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-JABagmPu-1681567233333)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/1682c55a-64fa-4819-8159-98e2f3f4887f.png)]
剩余网络的一个组成部分
与 VGG-19 相比,这是完整的 ResNet 架构。 点缀的跳过连接显示尺寸增加; 因此,为了使添加有效,不执行填充。 同样,尺寸的增加由颜色的变化表示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uS6T1lcj-1681567233333)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/e8526d3b-0776-40c7-8c03-aa7933a3889c.png)]
到目前为止,我们已经讨论过的 CNN 模型的所有变体都可以在 Keras 和 TensorFlow 中作为预训练模型使用。 我们将在我们的迁移学习应用中大量使用它们。 这是用于加载各种 VGG 模型的 Keras 代码段。 可以在这里中找到更多内容:
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-InYDkezg-1681567233333)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/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 x 20
。第二个卷积层又具有 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)对于每十个类别有一个十六维的胶囊,这些胶囊中的每一个都从主胶囊层中的所有胶囊接收输入。
胶囊的输出向量的长度表示由胶囊表示的实体存在于当前输入中的概率。 胶囊向量的长度被归一化,并保持在 0 到 1 之间。此外,在向量的范数上使用了压缩函数,以使短向量缩小到几乎零长度,而长向量缩小到略小于 1 的长度。
压缩函数为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-apeZSEYY-1681567233334)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/f9d8bcc5-8454-4231-a9eb-defa4e9761d3.png)]
其中x
是向量的范数,因此x > 0
(请参见下图):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RKfzHKWp-1681567233334)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/9b0a1cc5-3e19-4f90-9f9d-70dc2ecafe72.png)]
W[ij]
是每个初级胶囊中u[i]
,i ∈ (1, 32 x 6 x 6)
和v[j]
,在 DigitCaps 中j ∈ (1, 10)
。 在此,u_hat[j|i] = w[ij]u[i]
被称为预测向量,并且像一个已转换(旋转/翻译)的输入胶囊向量u[i]
。 胶囊的总输入s[j]
是来自下一层胶囊的所有预测向量的加权总和。 这些权重c[ij]
的总和为 1,在 Hinton 中称为耦合系数。 一开始,它假设胶囊i
应该与母体胶囊j
结合的对数先验概率对于所有i
和j
都是相同的,用b[ij]
表示。 因此,可以通过此众所周知的 softmax 转换来计算耦合系数:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OFpGZgsu-1681567233334)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/0890948e-245c-4806-90f2-b79e2218dbbe.png)]
通过称为协议路由的算法,这些耦合系数与网络的权重一起迭代更新。 简而言之,它执行以下操作:如果主胶囊i
的预测向量与可能的父级j
的输出具有大的标量积,则耦合系数b[ij]
对于该父对象增加而对于其他父对象减少。
完整的路由算法在这里给出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OsIQDwcW-1681567233334)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/5dda55cb-a1a3-4ce4-a112-ec0d16c5466f.png)]
左侧显示了如何通过权重矩阵W[ij]
将所有主胶囊连接到数字胶囊。 此外,它还描述了如何通过非线性压缩函数计算耦合系数以及如何计算 DigitCaps 的十六维输出。 在右侧,假设主胶囊捕获了两个基本形状:输入图像中的三角形和矩形。 旋转后将它们对齐会根据旋转量给出房屋或帆船。 很明显,这两个物体在极少或几乎没有旋转的情况下结合在一起,形成了帆船。 即,两个主胶囊比房屋更对准以形成船。 因此,路由算法应更新b[i, 船]
的耦合系数:
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-FnuR2Gal-1681567233335)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/eca8cda7-e58c-4af2-ad66-5d3f9208be75.png)]
如果存在第k
位,则T[k] = 1
。 m+ = 0.9
,m- = 0.1
。 λ
用于降低缺少数字类别的损失的权重。 与L[k]
一起,图像重建误差损失被用作网络的正则化。 如 CapsNet 架构所示,数字胶囊的输出被馈送到由三个完全连接的层组成的解码器中。 逻辑单元的输出与原始图像像素强度之间的平方差之和最小。 重建损失缩小了 0.0005 倍,因此在训练过程中它不控制边际损失。
循环神经网络(RNN)专用于处理一系列值,如x(1) ... x(t)
。 例如,如果要在给定序列的最新历史的情况下预测序列中的下一项,或者将一种语言的单词序列翻译为另一种语言,则需要进行序列建模。 RNN 与前馈网络的区别在于其架构中存在反馈环路。 人们常说 RNN 有记忆。 顺序信息保留在 RNN 的隐藏状态中。 因此,RNN 中的隐藏层是网络的内存。 从理论上讲,RNN 可以任意长的顺序使用信息,但实际上,它们仅限于回顾一些步骤。
我们将在后面解释:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5XM9pfZ6-1681567233335)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/3fe6be82-124d-4f5d-9594-db0a4b4c22af.png)]
通过展开网络中的反馈回路,我们可以获得前馈网络。 例如,如果我们的输入序列的长度为 4,则可以按以下方式展开网络。 展开相同的权重集后,在所有步骤中共享U
,V
和W
,与传统 DNN 不同。 因此,实际上我们在每个步骤都执行相同的任务,只是输入的内容不同。 这大大减少了我们需要学习的参数总数。 现在,要学习这些共享的权重,我们需要一个损失函数。 在每个时间步长处,我们都可以将网络输出y(t)
与目标序列s(t)
进行比较,并得出误差E(t)
。 因此,总误差为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3k9WKX1u-1681567233335)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/8d0d1fbc-ce0d-4beb-93f9-259f72be5b1f.png)]:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1AV87Tef-1681567233336)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/74d14517-90ed-4828-aad4-1cb89b7b96e1.png)]
让我们看一下使用基于梯度的优化算法学习权重所需的总误差导数。 我们有h[t] = Uφh[t-1] + Wx[t]
,Φ
是非线性激活,而y[t] = Vφh[t]
。
现在是:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AEzrlToq-1681567233336)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/c591f7f1-05aa-4bda-8f15-b98ea25a8336.png)]
根据链式规则,我们有:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r6KGvILC-1681567233336)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/1b5b0979-6df5-4b37-ba08-82f68f8c5558.png)]
在此,雅可比行列式∂h[t]/∂h[k]
,即层t
相对于前一层k
本身是雅可比行列式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzNLwrcK-1681567233337)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/2abf6ed9-391a-4e77-a7d4-5b5757349a19.png)]
的乘积。
使用前面的h[t]
方程,我们有:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8cD8MJAn-1681567233337)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/eda57a9e-cf19-4646-bd30-9138f3371662.png)]
因此,雅可比式∂h[t]/∂h[k]
的范数由乘积
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WeBqnx7Z-1681567233337)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/044603aa-f36f-4c54-b019-71057b3fb0b1.png)]
给出。 如果数量||∂h[s]/∂h[s-1]||
小于 1,则在较长的序列(例如 100 步)中,这些规范的乘积将趋于零。 同样,如果范数大于 1,则长序列的乘积将成倍增长。 这些问题在 RNN 中称为消失梯度和梯度爆炸。 因此,在实践中,RNN 不能具有很长的记忆。
随着时间的流逝,RNN 开始逐渐失去历史背景,因此很难进行实际训练。 这就是 LSTM 出现的地方! LSTM 由 Hochreiter 和 Schmidhuber 于 1997 年引入,可以记住来自非常长的基于序列的数据中的信息,并可以防止梯度消失等问题。 LSTM 通常由三个或四个门组成,包括输入,输出和忘记门。
下图显示了单个 LSTM 单元的高级表示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BMjJXBhE-1681567233338)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/28d6b1a5-598b-4a4b-8941-33a4ee4718dd.png)]
输入门通常可以允许或拒绝进入的信号或输入以更改存储单元状态。 输出门通常会根据需要将该值传播到其他神经元。 遗忘门控制存储单元的自循环连接以根据需要记住或忘记以前的状态。 通常,将多个 LSTM 单元堆叠在任何深度学习网络中,以解决现实世界中的问题,例如序列预测。 在下图中,我们比较了 RNN 和 LSTM 的基本结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y9R7yANf-1681567233338)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/c2f733e7-5563-4092-aeec-2e286cba3f6d.png)]
下图显示了 LSTM 单元和信息流的详细架构。 令t
表示一个时间步长; C
为单元状态;h
为隐藏状态。 通过称为门的结构,LSTM 单元具有删除信息或向单元状态添加信息的能力。 门i
,f
和o
分别代表输入门,忘记门和输出门,它们中的每一个都由 Sigmoid 层调制, 输出从零到一的数字,控制这些门的输出应通过多少。 因此,这有助于保护和控制单元状态:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oER1ilsH-1681567233338)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/572ea417-a601-4d49-b75c-24e4f2f6a6e0.png)]
通过 LSTM 的信息流包括四个步骤:
h[t]
,x[t-1]
,并将其输出通过 Sigmoid 挤压函数传递,以得到一个介于 0 和 1 之间的数字。 单元状态C[t-1]
。 1 表示应保留内存,零表示应完全擦除内存。
i[t]
来确定将信息写入哪个位置。接下来,tanh 层将创建新的候选信息。 书面。
f[t]
,擦除确定为可忘记的内容。 然后,在通过i[t]
缩放它们之后,添加在步骤 2 中计算的新状态信息。
我建议您在查看 Christophers 的博客,以获取有关 LSTM 步骤的更详细说明。 我们在这里查看的大多数图表均来自此。
LSTM 可以用于序列预测以及序列分类。 例如,我们可以预测未来的股价。 另外,我们可以使用 LSTM 构建分类器,以预测来自某些健康监控系统的输入信号是致命还是非致命信号(二分类器)。 我们甚至可以使用 LSTM 构建文本文档分类器。 单词序列将作为 LSTM 层的输入,LSTM 的隐藏状态将连接到密集的 softmax 层作为分类器。
如果我们想了解顺序数据的分层表示,可以使用 LSTM 层的栈。 每个 LSTM 层输出一个向量序列,而不是序列中每个项的单个向量,这些向量将用作后续 LSTM 层的输入。 隐藏层的这种层次结构使我们的顺序数据可以更复杂地表示。 堆叠的 LSTM 模型可用于对复杂的多元时间序列数据进行建模。
机器翻译是计算语言学的一个子领域,涉及将文本或语音从一种语言翻译成另一种语言。 传统的机器翻译系统通常依赖于基于文本统计属性的复杂特征工程。 最近,深度学习已被用于解决此问题,其方法称为神经机器翻译(NMT)。 NMT 系统通常由两个模块组成:编码器和解码器。
它首先使用编码器读取源句子,以构建思想向量:代表该句子含义的数字序列。 解码器处理句子向量以发出对其他目标语言的翻译。 这称为编码器-解码器架构。 编码器和解码器通常是 RNN 的形式。 下图显示了使用堆叠 LSTM 的编码器-解码器架构。 在这里,第一层是一个嵌入层,用于通过密集的实向量来表示源语言中的单词。 为源语言和目标语言都预定义了词汇表。 不在词汇表中的单词由固定单词<未知>
表示,并由固定嵌入向量表示。 该网络的输入首先是源句子,然后是句子标记<s>
的结尾,指示从编码模式到解码模式的转换,然后馈入目标句子:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yUsN69DX-1681567233338)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/9c3d34f8-aa07-489d-b95e-1d215daf0dee.png)]
数据来源:https://www.tensorflow.org/tutorials/seq2seq
输入嵌入层后面是两个堆叠的 LSTM 层。 然后,投影层将最上面的隐藏状态转换为尺寸为V
(目标语言的词汇量)的对率向量。 这里,交叉熵损失用于通过反向传播训练网络。 我们看到在训练模式下,源句子和目标句子都被输入到网络中。 在推理模式下,我们只有源句。 在那种情况下,可以通过几种方法来完成解码,例如贪婪解码,与贪婪解码结合的注意力机制以及集束搜索解码。 我们将在这里介绍前两种方法:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bdRMwcYL-1681567233339)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/52fe1877-dded-4161-8520-ba0d425f2452.png)]
在贪婪的解码方案中(请参见前面两个图的左手图),我们选择最有可能的单词(以最大对率值描述为发射的单词),然后将其反馈给解码器作为输入。 继续该解码过程,直到产生句子结束标记</s>
作为输出符号。
由源句子的句子结尾标记生成的上下文向量必须对我们需要了解的有关源句子的所有内容进行编码。 它必须充分体现其含义。 对于长句子,这意味着我们需要存储非常长的记忆。 研究人员发现,反转源序列或两次馈入源序列有助于网络更好地记忆事物。 对于与英语非常相似的法语和德语这样的语言,反转输入是有意义的。 对于日语,句子的最后一个单词可能会高度预测英语翻译中的第一个单词。 因此,这里的反转会降低翻译质量。 因此,一种替代解决方案是使用注意机制(如前两个图的右图所示)。
现在,无需尝试将完整的源句子编码为固定长度的向量,而是允许解码器在输出生成的每个步骤中使参与到源句子的不同部分。 因此,我们将第t
个目标语言单词的基于注意力的上下文向量c[t]
表示为所有先前源隐藏状态的加权和:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DJg3FVX4-1681567233339)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/823d1b46-4dad-4194-b79e-9cef6d5164fa.png)]
注意权重为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dExSWr3j-1681567233339)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/d2be8a4c-1bd0-415e-ae4f-39d41a0985df.png)]
得分的计算如下:sc(h[t], h[s]) = h[t] W h[s]
。
W
是权重矩阵,将与 RNN 权重一起学习。 该得分函数称为 Luong 的乘法样式得分。 此分数还有其他一些变体。 最后,通过将上下文向量与当前目标隐藏状态组合如下来计算注意向量,a[t]
:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xgQ8IWA8-1681567233340)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/4e505ad7-0de3-474e-b874-95c380aec9b5.png)]
注意机制就像只读存储器,其中存储了源的所有先前隐藏状态,然后在解码时读取它们。 TensorFlow 中 NMT 的源代码可在此处获得。
门控循环单元(GRU)与 LSTM 相关,因为两者均利用不同的门控信息方式来防止梯度消失和存储长期记忆。 GRU 具有两个门:重置门r
和更新门z
,如下图所示。 复位门确定如何将新输入与先前的隐藏状态h[t-1]
组合在一起,而更新门则确定要保留多少先前状态信息。 如果我们将重置设置为全 1 并将更新门更新为全零,我们将得到一个简单的 RNN 模型:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-doZGEGJU-1681567233340)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/35710e6e-1f70-4ab8-9727-9c318f0e0d4f.png)]
GRU 相对较新,其表现与 LSTM 相当,但由于结构更简单,参数更少,因此它们在计算上更加高效。 这是 LSTM 和 GRU 之间的一些结构差异:
C[t]
。如果有足够的数据,建议使用 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-XkbxH0D9-1681567233340)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/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-dZZrZszn-1681567233341)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/113b62ab-656d-4d22-9e1c-26c2855cf9e5.png)]
其中s[0]
是输入q
和m[i]
之间的评分函数,o1
是具有最佳匹配的内存m
的索引。 现在,使用查询和第一个检索到的内存,我们可以检索下一个内存m[o2]
,这两个内存都很接近:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AfdmjCNe-1681567233341)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/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-xwpXtJQD-1681567233341)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/7edbfaa7-ef11-4918-b0bf-23ecb6bb5098.png)]
这种模型很难使用反向传播来进行端到端训练,并且需要在网络的每个模块上进行监督。 对此有一点修改,实际上是称为端到端存储网络(MemN2N)的连续版本的存储网络。 该网络可以通过反向传播进行训练。
我们从一个查询开始:牛奶现在在哪里? 使用大小为V
的向量,用成袋的单词进行编码。 在最简单的情况下,我们使用嵌入B(d x V)
将向量转换为大小为d
的词嵌入。 我们有u = embeddingB(q)
:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vUzQ5I0A-1681567233341)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/d6252a46-1170-4bd3-a61c-ec067acd5310.png)]
输入句子x1, x2, ..., xi
通过使用另一个嵌入矩阵A(d x V)
存储在内存中,其大小与B[mi] = embeddingA(x[i])
。 每个嵌入式查询u
与每个内存m[i]
之间的相似度是通过取内积和 softmax 来计算的:p[i] = softmax(u^T m[i])
。
输出存储器表示如下:每个x[i]
具有对应的输出向量c[i]
,可以用另一个嵌入矩阵C
表示。 然后,来自存储器的响应向量o
是c[i]
上的总和,并由来自以下输入的概率向量加权:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tZwQpZpF-1681567233342)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/f448c97e-6ab7-4718-90ff-cc360122bce3.png)]
最后,将o
和u
之和与权重矩阵W(V x d)
相乘。 结果传递到 softmax 函数以预测最终答案:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vu48ZcIm-1681567233342)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/00a99297-3d3b-4387-a888-ff58f137672d.png)]
神经图灵机(NTM)受到图灵机(TM)的启发:定义了一个抽象机。 TM 可以根据规则表来操作一条胶带上的符号。 对于任何计算机算法,TM 都可以模拟该算法的逻辑。 机器将其头放在单元格上方并在其中读取或写入符号。 此后,根据定义的规则,它可以向左或向右移动甚至停止程序。
NTM 架构包含两个基本组件:神经网络控制器和内存。 下图显示了 NTM 架构的高层表示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yLuj2AC6-1681567233342)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/08a67c09-f33c-4ff9-aa7c-0d8d43e74d6d.png)]
控制器使用输入和输出向量与外部世界进行交互。 与标准神经网络不同,此处的控制器还使用选择性读取和写入操作与存储矩阵进行交互。 内存是一个实值矩阵。 内存交互是端到端可区分的,因此可以使用梯度下降对其进行优化。 NTM 可以从输入和输出示例中学习简单的算法,例如复制,排序和关联召回。 而且,与 TM 不同,NTM 是可通过梯度下降训练的可微分计算机,为学习程序提供了一种实用的机制。
控制器可以由 LSTM 建模,LSTM 具有自己的内部存储器,可以补充矩阵中更大的存储器。 可以将控制器与计算机中的 CPU 相比较,并且可以将存储矩阵与计算机的 RAM 相比较。
读写头选择要读取或写入的内存部分。 可以通过神经网络中的隐藏层(可能是 softmax 层)对它们进行建模,以便可以将它们视为外部存储单元上的权重之和,这些权重之和为 1。此外,请注意,模型参数的数量是受控的,不会随存储容量的增长而增加。
控制器输出用于确定要读取或写入的存储器位置。 这由一组分布在所有内存位置上的权重定义,这些权重之和为 1。权重由以下两种机制定义。 想法是为控制器提供几种不同的读取或写入内存的模式,分别对应于不同的数据结构:
k
输出与所有存储位置进行比较,然后所有距离均由 softmax 归一化,得到和为一的权重:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QKRdGX9X-1681567233342)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/9c2898fa-79f4-4806-a527-d2a1fbf1f977.png)]
在这种情况下,β ≥ 1
称为清晰度参数,并控制对特定位置的聚焦。 它还为网络提供了一种方法来决定其希望内存位置访问的精确度。 就像模糊均值聚类中的模糊系数。
s
(即[-n, n]
上的 softmax),将其与先前计算的存储器权重进行卷积以产生移位的存储器位置,如下图所示。 这种转变是循环的; 也就是说,它环绕边界。 下图是内存的热图表示—较深的阴影表示更多的权重:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UFIzIGIX-1681567233342)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/0952f060-fd33-4b79-a530-a3ef4c96a006.png)]
在应用旋转移位之前,将内容寻址所给定的权重向量与先前的权重向量w[t-1]
相结合,如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nvwmSVml-1681567233343)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/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-4wOb7KNY-1681567233343)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/1e4d609a-d3bb-41ee-8b63-6e137fa2eb4a.png)]
M
读取向量r[t]
的长度定义为行向量M[t](i)
的凸组合,在内存中:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pFMXH1Gl-1681567233343)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/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)
。
这是上述操作的伪代码:
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 实现可在此处获得。 NTM 算法可以学习复制-他们可以学习复制随机数序列的算法。 下面显示了 NTM 如何使用内存读写头并将其移位以实现复制算法:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n1yLQjrs-1681567233343)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/0284727e-ebf8-4bc9-8074-24022d4a2558.png)]
类似地,给定一组随机序列和相应的排序序列,NTM 可以从数据中高效地学习排序算法。
我们已经讨论了基于注意力的机器翻译模型。 基于注意力的模型的优点在于,它们提供了一种解释模型并理解其工作方式的方式。 注意机制是记忆以前的内部状态的一种形式。 这就像内部存储器。 与典型的存储器不同,这里的存储器访问机制是软的,这意味着网络将检索所有存储器位置的加权组合,而不是单个离散位置的值。 软存储器访问使通过反向传播训练网络变得可行。 基于注意的架构不仅用于机器翻译,还可以用于自动生成图像标题。
这项工作于 2016 年发表在论文《Show,Attend and Tell:带有视觉注意的神经图像字幕生成》上,作者是 Kelvin Xu 及其合著者。 在这里,从注意力权重来看,我们看到随着模型生成每个单词,其注意力发生变化以反映图像的相关部分。 此关注模型的 TensorFlow 实现可在此处获得:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DAaL0ihy-1681567233343)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/9c470dba-210a-4bfd-8a95-1227d45362b9.png)]
本章介绍了神经网络架构的各种进展及其在各种实际问题中的应用。 我们讨论了对这些架构的需求,以及为什么简单的深度多层神经网络不能充分解决各种问题,因为它具有强大的表达能力和丰富的假设空间。 讨论迁移学习用例时,将在后面的章节中使用其中讨论的许多架构。 提供了几乎所有架构的 Python 代码参考。 我们还试图清楚地解释一些最近的架构,例如 CapsNet,MemNN 和 NTM。 当您逐步学习迁移学习用例时,我们将经常参考本章。
下一章将介绍转学的概念。
我还在学习。
——米开朗基罗
人类具有在任务之间传递知识的固有能力。 我们在学习一项任务时获得的知识就是我们以相同的方式来解决相关任务。 任务越相关,我们就越容易迁移或交叉利用知识。 到目前为止,到目前为止,机器学习和深度学习算法都是设计为独立工作的。 这些算法经过训练可以解决特定任务。 一旦特征空间分布发生变化,就必须从头开始重建模型。 迁移学习是克服孤立的学习范式,并利用一项任务获得的知识来解决相关任务的想法。 在本章中,我们将介绍迁移学习的概念,并专注于深度学习上下文的各个方面。 本章将涵盖以下主题:
传统上,学习算法设计为单独解决任务或问题。 根据用例和手头数据的要求,应用算法来训练给定特定任务的模型。 传统的机器学习(ML)根据特定的域,数据和任务单独地训练每个模型,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mJJ17cMX-1681567233344)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/0c813b73-4ddd-42c6-a8c1-79fc1597e8f2.png)]
传统机器学习
迁移学习使学习的过程更进一步,并且更加符合人类如何跨任务利用知识。 因此,迁移学习是一种将模型或知识重用于另一个相关任务的方法。 迁移学习有时也被视为现有 ML 算法的扩展。 在迁移学习的背景下,以及在了解如何在任务之间迁移知识的过程中,正在进行大量的研究和工作。 但是,神经信息处理系统(NIPS)1995 研讨会《学习:归纳系统中的知识整合和迁移》被认为是这个领域的研究。
从那时起,诸如元学习,知识整合和归纳迁移等术语已与迁移学习互换使用。 总是有不同的研究人员和学术著作提供不同背景下的定义。 在他们的书深度学习中,Goodfellow 等人。 在泛化的背景下指迁移学习。 它们的定义如下:
利用一种情况下所学的知识来改善另一种情况下的泛化的情况。
让我们借助示例来了解前面的定义。 假设我们的任务是在餐厅的受限区域内识别图像中的对象。 让我们在定义的范围内将此任务标记为T[1]
。 给定该任务的数据集,我们训练模型并对其进行调整,以使它在来自同一域(餐厅)的看不见的数据点上表现良好(概括)。 当我们在给定领域中没有足够的训练示例来完成所需的任务时,传统的监督 ML 算法就会崩溃。 假设我们现在必须从公园或咖啡馆中的图像中检测物体(例如,任务T[2]
)。 理想情况下,我们应该能够应用针对T[1]
训练的模型,但实际上,我们面临着表现下降和模型不能很好推广的问题。 发生这种情况的原因多种多样,我们可以广泛地和集体地将其称为模型对训练数据和领域的偏见。 因此,迁移学习使我们能够利用先前学习的任务中的知识,并将其应用于更新的相关任务。 如果我们拥有任务T[1]
的大量数据,则可以利用其学习并将其概括用于任务T[2]
(其数据要少得多)。 在图像分类的情况下,某些低级特征(例如边缘,形状和照明)可以在任务之间共享,因此可以在任务之间传递知识。
下图显示了迁移学习如何使现有知识重用于新的相关任务:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bpyg3hNr-1681567233344)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/8a932ea8-9a32-4475-8017-88d5e3fb917b.png)]
如上图所示,在学习目标任务时,来自现有任务的知识将作为附加输入。
我们利用源模型中的知识来改进目标任务中的学习。 除了提供重用已经构建的模型的功能之外,迁移学习还可以通过以下方式帮助学习目标任务:
读者应注意,有可能获得这些收益中的一项或多项,我们将在接下来的章节中详细讨论。 如下图所示,它显示出更好的基线表现(更高的起始),效率增益(更高的斜率)和更好的最终表现(渐近线更高) :
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xt3ZxeNQ-1681567233344)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/8f69a68a-52a7-4ae5-a2b1-019f7a344fc4.png)]
使用迁移学习的可能好处(来源:迁移学习,Lisa Torrey 和 Jude Shavlik)
迁移学习已在归纳学习器(例如神经网络和贝叶斯网络)的上下文中得到应用和研究。 强化学习是另一个探索迁移学习可能性的领域。 因此,迁移学习的概念不限于深度学习。
在本章及后续章节中,我们将限制使用迁移学习的范围仅限于深度学习的上下文。
首先让我们看一下迁移学习的正式定义,然后利用它来理解不同的策略。 在他们的论文《迁移学习综述》中,潘和杨使用域,任务和边缘概率,以提供用于理解迁移学习的框架。 该框架定义如下:
域D
定义为由特征空间χ
和边缘概率P(X)
组成的二元元组,其中X
是样本数据点。
在此,x = {x[1], x[2], .... x[n]}
,其中x[i]
作为X
的特定向量ε χ
。 从而:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FsWRD6t3-1681567233344)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/bedd445a-346d-4dd4-97f8-bf38e2fe755c.png)]
另一方面,可以将任务T
定义为标签空间γ
和目标函数f
的二元组。 从概率的观点来看,目标函数也可以表示为P(γ|Χ)
。 从而:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MevKoEDc-1681567233345)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/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(Υ[s] | Χ[s]) ≠ P(Υ[t] | Χ[t])
,因此源域和目标域中的条件概率不同。到目前为止,我们已经看到迁移学习具有在目标任务中利用来自源学习器的现有知识的能力。 在迁移学习过程中,必须回答以下三个重要问题:
Pan 和 Yang 撰写的论文《迁移学习综述》可以在此处找到。
分组技术可帮助我们了解整体特征并提供更好的框架来利用它们。 可以根据所涉及的传统 ML 算法的类型对迁移学习方法进行分类,例如:
上一节中讨论的三种迁移类别概述了可以应用和详细研究迁移学习的不同设置。 为了回答在这些类别中迁移什么的问题,可以采用以下一些方法:
在本节中,我们以非常通用的方式研究了在不同背景和环境下进行迁移学习的不同策略。 现在让我们利用这种理解,学习如何在深度学习的上下文中应用迁移学习。
深度学习模型代表了归纳学习。 归纳学习算法的目的是从一组训练示例中得出映射。 例如,在分类的情况下,模型学习输入特征和类标签之间的映射。 为了使这样的学习器能够很好地对看不见的数据进行概括,其算法采用了与训练数据的分布有关的一组假设。 这些假设集合称为感应偏置。
归纳偏差或假设可以由多个因素来表征,例如它所限制的假设空间以及通过假设空间进行的搜索过程。 因此,这些偏差会影响模型在给定任务和领域上的学习方式和知识。
归纳迁移技术利用源任务的归纳偏差来辅助目标任务。 这可以通过不同的方式来完成,例如通过限制模型空间,缩小假设空间来调整目标任务的归纳偏差,或者借助源任务的知识来对搜索过程本身进行调整。 下图直观地描述了此过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1IBLpdB-1681567233345)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/c65cedd9-a157-4389-8433-18945df2fc9d.png)]
归纳式传递(来源:传递学习,Lisa Torrey 和 Jude Shavlik)
除了归纳迁移,归纳学习算法还利用贝叶斯和层次迁移技术来帮助改进目标任务的学习和表现。
近年来,深度学习取得了长足的进步,其结果令人赞叹。 但是,这种深度学习系统所需的训练时间和数据量比传统的 ML 系统高出几个数量级。
跨计算机视觉和自然语言处理(NLP)。 在大多数情况下,团队/人员共享这些网络的详细信息以供其他人使用(第 3 章“了解深度学习架构”中共享了一些受欢迎的网络)。 这些预训练的网络/模型在深度学习的背景下构成了迁移学习的基础。
如第 3 章“了解深度学习架构”中所述,深度学习系统是分层的架构,可在不同的层学习不同的特征。 然后将这些层最终连接到最后一层(在分类的情况下,通常是完全连接的层)以获得最终输出。 这种分层的架构使我们可以利用预先训练的网络(例如 Inception V3 或 VGG),而无需将其最终层用作其他任务的固定特征提取器。 下图表示基于特征提取的深度迁移:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PVRf5DsR-1681567233345)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/d120bc80-6174-4e45-91a0-7f900fb3142f.png)]
例如,如果我们使用没有最终分类层的 AlexNet,它将帮助我们将新领域任务的图像基于其隐藏状态转换为 4,096 维向量,从而使我们能够利用来自源域任务的知识,从新领域任务中提取特征。 这是使用深度神经网络执行迁移学习的最广泛使用的方法之一。
这是一项涉及更多的技术,我们不仅要替换最后一层(用于分类/回归),而且还要选择性地重新训练一些先前的层。 深度神经网络是具有各种超参数的高度可配置的架构。 如前所述,最初的层已捕获通用特征,而后面的层则更多地关注手头的特定任务。 利用这种洞察力,我们可以在重新训练时冻结(固定权重)某些层,或者微调其余层以满足我们的需求。 在这种情况下,我们利用网络整体架构方面的知识,并将其状态用作我们再训练步骤的起点。 反过来,这有助于我们以更少的训练时间获得更好的表现。
迁移学习的基本要求之一是在源任务上表现良好的模型的存在。 幸运的是,深度学习世界相信共享。 他们各自的团队已经公开共享了许多最先进的深度学习架构。 这些跨不同领域,例如计算机视觉和 NLP。 我们在第 3 章“了解深度学习架构”中介绍了一些最著名和文档最丰富的架构。 这些网络背后的团队不仅分享了结果,而且分享了他们的预训练模型。 预训练模型通常以数百万个参数/权重的形式共享,该模型在被训练到稳定状态时所达到的模型。 预训练的模型可供每个人通过不同的方式使用。 著名的深度学习 Python 库keras
提供了下载各种可用的预训练网络的接口,例如 XCeption,VGG16 和 InceptionV3。 同样,也可以通过 TensorFlow 和其他深度学习库获得预训练的模型。 伯克利的模型动物园提供了多年来开发的更广泛的预训练模型集合。
深度学习是一类算法,已被用来非常成功地获得迁移学习的好处。 以下是一些示例:
关于迁移学习的文献经历了许多迭代,并且如本章开头所提到的,与迁移学习相关的术语被松散使用并且经常可以互换使用。 因此,有时难以区分迁移学习,领域适应和多任务学习。 放心,这些都是相关的,并尝试解决类似的问题。 为了使本书保持一致,我们将采用迁移学习的概念作为一般概念,在此我们将尝试使用源任务域知识来解决目标任务。
域适配通常是指源域和目标域之间的边缘概率不同的情况,例如P(X[s]) ≠ P(X[t])
。 源域和目标域的数据分布存在固有的偏移或漂移,需要进行调整才能迁移学习。 例如,标记为肯定或否定的电影评论语料库将不同于产品评论情感的语料库。 如果用于对产品评论进行分类,则经过电影评论情感训练的分类器将看到不同的分布。 因此,在这些情况下,领域自适应技术可用于迁移学习中。
我们学习了不同的迁移学习策略,甚至讨论了如何将知识从源迁移到目标的三个问题。 特别是,我们讨论了特征表示传递如何有用。 值得重申的是,深度学习网络中的不同层捕获了不同的特征集。 我们可以利用这一事实来学习领域不变的特征,并提高其跨领域的可移植性。 代替让模型学习任何表示,我们将两个域的表示微调为尽可能相似。
这可以通过将某些预处理步骤直接应用于表示本身来实现。 孙宝琛,冯家石和 Kate Saenko 在他们的论文 《令人沮丧的轻松域自适应》中讨论了其中一些问题。 Ganin 等人也提出了对表示相似性的轻推,在他们的论文《神经网络领域专家训练》中。 该技术背后的基本思想是在源模型中添加另一个目标,以通过混淆域本身来鼓励相似性,从而使域混淆。
多任务学习与迁移学习世界略有不同。 在多任务学习的情况下,可以同时学习多个任务,而无需区分源和目标。 在这种情况下,与迁移学习相比,学习器一次就接收到有关多个任务的信息,在迁移学习中,学习器最初对目标任务一无所知。
如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-64cFqLWf-1681567233345)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/93186224-8b4d-4459-98e7-a21fd5aaf615.png)]
多任务学习:学习器同时从所有任务接收信息
深度学习系统天生就渴望数据,因此它们需要许多训练示例来学习权重。 这是深度神经网络的局限性之一,尽管人类学习并非如此。 例如,一旦向孩子展示了苹果的外观,他们就可以轻松识别出不同种类的苹果(带有一个或几个训练示例); 机器学习和深度学习算法并非如此。 单样本学习是迁移学习的一种变体,在这种学习中,我们尝试仅根据一个或几个训练示例来推断所需的输出。 这在无法为每个可能的类提供标签数据的现实世界场景中(如果是分类任务)和在经常可以添加新类的场景中非常有用。
据说 Fei-Fei 及其合作者具有里程碑意义的论文《对象类别的单发学习》。 在此子领域创造了“单样本学习和研究”一词。 本文提出了一种用于对象分类的表示学习的贝叶斯框架的变体。 此后,此方法已得到改进,并已使用深度学习系统进行了应用。
零镜头学习是迁移学习的另一个极端变体,它不依赖任何标记的示例来学习任务。 这听起来令人难以置信,尤其是当使用示例学习是大多数监督学习算法所要解决的问题时。 零数据学习或零短学习方法在训练阶段本身进行了巧妙的调整,以利用附加信息来理解看不见的数据。 在他们的《学会学习》一书中,Goodfellow 及其合作者提出了零样本学习作为学习三个变量的场景,例如传统输入变量x
,传统输出变量y
以及描述任务的其他随机变量T&
。 因此,训练模型以学习P(y | x, T)
的条件概率分布。 零镜头学习在机器翻译等场景中非常有用,在这种情况下,我们甚至可能没有目标语言的标签。
迁移学习具有巨大的潜力,并且是现有学习算法通常需要的增强。 但是,与迁移学习相关的某些相关问题需要更多的研究和探索。 除了难以回答关于什么,什么时候以及如何迁移的问题之外,负迁移和迁移界限也带来了重大挑战。
到目前为止,我们讨论的案例都是基于源任务的知识迁移来实现目标任务的改进。 在某些情况下,迁移学习会导致表现下降。 负迁移是指从源到目标的知识迁移不会导致任何改善,而是导致目标任务的整体表现下降的情况。 负迁移的原因可能有多种,例如源任务与目标任务的关系不充分或迁移方法不能很好地利用源任务和目标任务之间的关系的情况。 避免负迁移非常重要,需要仔细调查。 在他们的工作中,Rosenstien 及其合作迁移经验上介绍了当源与目标过于不同时,暴力传递如何降低目标任务的表现。 正在研究 Bakker 及其合作者的贝叶斯方法,以及探索基于聚类的解决方案以识别相关性的其他技术,以避免产生负迁移。
在迁移学习中量化迁移也非常重要,这对迁移的质量及其可行性具有影响。 为了衡量迁移的数量,哈桑·马哈茂德(Hassan Mahmud)及其合作者使用 Kolmogorov 复杂度证明了一定的理论界限,以分析迁移学习并衡量任务之间的相关性。 Eaton 及其合作者提出了一种新颖的基于图的方法来衡量知识迁移。 这些技术的详细讨论超出了本书的范围。 鼓励读者使用本节概述的出版物来探讨这些主题。
在本书的第 1 章至第 3 章中设置了 ML 和深度学习的上下文和基础之后,本章开始了第二阶段的学习,即建立迁移学习的基础。 在深入研究实际用例之前,必须正式化对迁移学习的理解,并了解不同的技术和研究以及与之相关的挑战。 在本章中,我们介绍了迁移学习概念背后的基础知识,多年来的发展情况以及为什么首先需要迁移学习。
我们首先在学习算法及其相关优势的更广泛背景下理解迁移学习。 然后,我们讨论了用于理解,应用和分类迁移学习方法的各种策略。 在深度学习的背景下,迁移学习是下一个讨论的主题,为本章的其余部分定下了基调。 我们讨论了与深度迁移学习相关的不同迁移学习方法,例如特征提取和微调。 我们还介绍了著名的预训练模型和使用深度学习系统的迁移学习的流行应用。 近年来,深度学习已被证明是非常成功的,因此,在此领域中使用迁移学习已进行了大量研究。
我们简要讨论了深度迁移学习的不同变体,例如域自适应,域混淆,多任务学习,单样本学习, 和零样本学习。 我们通过介绍与迁移学习相关的挑战(例如负迁移和迁移边界)来结束本章。 在本章中,我们概述了与迁移学习相关的各种研究出版物和链接,并鼓励读者探索它们以获取更多信息。 本章将作为当前过渡学习领域的指导和概述。 敬请关注下一章的更多细节,我们将提供一些与转学相关的动手练习。
在上一章中,我们介绍了围绕迁移学习的主要概念。 关键思想是,与从头开始构建自己的深度学习模型和架构相比,在各种任务中利用先进的,经过预训练的深度学习模型可产生更好的结果。 在本章中,我们将获得一个更动手的观点,即使用迁移学习实际构建深度学习模型并将其应用于实际问题。 有无迁移学习,我们将构建各种深度学习模型。 我们将分析它们的架构,并比较和对比它们的表现。 本章将涵盖以下主要方面:
我们要感谢 Francois Chollet 不仅创建了令人惊叹的深度学习框架 Keras,还感谢他在他的书《Python 深度学习》中谈到了有效学习迁移的现实世界问题。 在本章中,我们以此为灵感来刻画了迁移学习的真正力量。 本章的代码将在 GitHub 存储库中的文件夹中提供,根据需要遵循本章。
我们已经在第 4 章“迁移学习基础”中简要讨论了迁移学习的优势。 概括地说,与从头开始构建深度学习模型相比,我们获得了一些好处,例如,改善了基准表现,加快了整体模型的开发和训练时间,并且还获得了整体改进和优越的模型表现。 这里要记住的重要一点是,迁移学习作为一个领域早已在深度学习之前就存在了,并且还可以应用于不需要深度学习的领域或问题。
现在让我们考虑一个现实世界的问题,在本章中,我们还将继续使用它来说明我们不同的深度学习模型,并在同一模型上利用迁移学习。 您必须一次又一次听到深度学习的关键要求之一是,我们需要大量数据和样本来构建可靠的深度学习模型。 其背后的想法是模型可以从大量样本中自动学习特征。 但是,如果我们没有足够的训练样本并且要解决的问题仍然是一个相对复杂的问题,我们该怎么办? 例如,计算机视觉问题,例如图像分类,可能难以使用传统的统计技术或机器学习(ML)技术解决。 我们会放弃深度学习吗?
考虑到图像分类问题,由于我们要处理的图像本质上是高维张量,因此拥有更多数据可使深度学习模型学习更好的图像基本特征表示。 但是,即使我们每个类别的图像样本的范围从几百到数千,基本的 CNN 模型在正确的架构和规范化条件下仍能正常运行。 这里要记住的关键点是,CNN 会学习与缩放,平移和旋转不变的模式和特征,因此我们在这里不需要自定义特征工程技术。 但是,我们可能仍然会遇到模型过拟合之类的问题,我们将在本章稍后部分尝试解决这些问题。
关于迁移学习,已经在著名的 ImageNet 数据集上训练了一些出色的预训练深度学习模型。 我们已经在第 3 章“了解深度学习架构”中详细介绍了其中一些模型,本章将利用著名的VGG-16
模型。 想法是使用通常是图像分类专家的预训练模型来解决我们的问题,即数据样本较少。
正如我们前面提到的,我们将在图像分类问题上进行工作,每个类别的训练样本数量较少。 我们的问题的数据集可在 Kaggle 上获得,它是其中最受欢迎的基于计算机视觉的数据集之一。 我们将使用的数据集来自猫狗挑战,而我们的主要目标是建立一个可以成功识别图像并将其分类为猫或狗的模型。 就机器学习而言,这是一个基于图像的二分类问题。
首先,从数据集页面下载train.zip
文件并将其存储在本地系统中。 下载后,将其解压缩到文件夹中。 该文件夹将包含 25,000 张猫和狗的图像; 即每个类别 12500 张图像。
虽然我们可以使用所有 25,000 张图像并在它们上建立一些不错的模型,但是,如果您还记得的话,我们的问题目标包括增加的约束,即每类图像的数量很少。 为此,我们构建自己的数据集。 如果您想自己运行示例,可以参考Datasets Builder.ipynb
Jupyter 笔记本。
首先,我们加载以下依赖项,包括一个名为utils
的工具模块,该模块在本章代码文件中的utils.py
文件中可用。 当我们将图像复制到新文件夹时,这主要用于获得视觉进度条:
import glob
import numpy as np
import os
import shutil
from utils import log_progress
np.random.seed(42)
现在,如下所示将所有图像加载到原始训练数据文件夹中:
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 张图像用于我们的测试数据集(两个动物类别的表示均相同):
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,)
现在我们已经创建了数据集,让我们将它们写到单独文件夹中的磁盘中,以便我们将来可以在任何时候返回它们,而不必担心它们是否存在于主内存中:
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-k7fnSb6t-1681567233345)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/3a4d72af-af1e-4c54-810d-6190c28141c9.png)]
由于这是图像分类问题,因此我们将利用 CNN 模型或 convNets 尝试解决此问题。 在本章开始时,我们简要讨论了我们的方法。 我们将从头开始构建简单的 CNN 模型,然后尝试使用正则化和图像增强等技术进行改进。 然后,我们将尝试利用预训练的模型来释放转学的真正力量!
让我们开始构建图像分类分类器。 我们的方法是在训练数据集上建立模型,并在验证数据集上进行验证。 最后,我们将在测试数据集上测试所有模型的表现。 在进入建模之前,让我们加载并准备数据集。 首先,我们加载一些基本的依赖项:
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
现在,使用以下代码片段加载数据集:
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)
之间的值,因为深度学习模型在较小的输入值下确实可以很好地工作:
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-9yFjTnQx-1681567233346)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/cec84552-8b64-4c85-a3c1-41ebd47bfb10.png)]
前面的输出显示了我们训练数据集中的示例图像之一。 现在,让我们设置一些基本的配置参数,并将文本类标签编码为数值(否则,Keras 将抛出错误):
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 模型开始,再加上用于从图像中自动提取特征的最大池化,以及对输出卷积特征图进行下采样。 要刷新有关卷积和池化层如何工作的记忆,请查看第 3 章“了解深度学习架构”中的 CNN 部分。
提取这些特征图后,我们将使用一个密集层以及一个具有 S 型函数的输出层进行分类。 由于我们正在执行二分类,因此binary_crossentropy
损失函数就足够了。 我们将使用流行的 RMSprop 优化器,该优化器可帮助我们使用反向传播来优化网络中单元的权重,从而使网络中的损失降到最低,从而得到一个不错的分类器。 请参阅第 2 章,“深度学习要点”中的“随机梯度下降”和“SGD 改进”部分,以获取有关优化器如何工作的深入见解。 简而言之,优化器(如 RMSprop)指定有关损耗梯度如何用于更新传递到我们网络的每批数据中的参数的规则。
让我们利用 Keras 并立即构建我们的 CNN 模型架构:
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 张图像的验证集上进行了验证:
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
根据训练和验证的准确率值,我们的模型似乎有点过拟合。 我们可以使用以下代码段绘制模型的准确率和误差,以获得更好的视角:
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")
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-sdudWwLP-1681567233346)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/d38f922d-b1ed-41c4-888c-146a5fa4c0a1.png)]
您可以清楚地看到,在 2-3 个周期之后,模型开始对训练数据进行过拟合。 我们在验证集中获得的平均准确率约为 72%,这不是一个不好的开始! 我们可以改进此模型吗?
让我们通过增加一个卷积层,另一个密集的隐藏层来改进我们的基本 CNN 模型。 除此之外,我们将在每个隐藏的密集层之后添加 0.3 的差值以启用正则化。 我们在第 2 章“深度学习基础知识”中简要介绍了丢弃法问题,因此随时可以快速浏览一下它,以备不时之需。 基本上,丢弃法是在深度神经网络中进行正则化的有效方法。 它可以分别应用于输入层和隐藏层。
通过将输出的输出设置为零,丢弃法随机掩盖了一部分设备的输出(在我们的示例中,它是密集层中 30% 的设备的输出):
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'])
现在,让我们在训练数据上训练新模型,并在验证数据集上验证其表现:
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-hAA1JDL0-1681567233346)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/3f0bcdb3-568a-48e2-8003-b2cd4f4f4b61.png)]
从前面的输出中您可以清楚地看到,尽管模型花费了更长的时间,但仍然最终使模型过拟合,并且我们还获得了约 78% 的更好的验证精度,这虽然不错,但并不令人惊讶。
模型过拟合的原因是因为我们的训练数据少得多,并且模型在每个周期随着时间的推移不断看到相同的实例。 解决此问题的一种方法是利用图像增强策略,以与现有图像略有不同的图像来增强我们现有的训练数据。 我们将在下一节中详细介绍。 让我们暂时保存该模型,以便以后可以使用它来评估其在测试数据上的表现:
model.save('cats_dogs_basic_cnn.h5')
让我们通过使用适当的图像增强策略添加更多数据来改进我们的常规 CNN 模型。 由于我们先前的模型每次都在相同的小数据点样本上进行训练,因此无法很好地推广,并在经过几个周期后最终过拟合。
图像增强背后的想法是,我们遵循一个既定过程,从训练数据集中获取现有图像,并对它们应用一些图像变换操作,例如旋转,剪切,平移,缩放等,以生成现有图像的新的,经过修改的版本。 由于这些随机转换,我们每次都不会获得相同的图像,我们将利用 Python 生成器在训练过程中将这些新图像提供给我们的模型。
Keras 框架具有一个称为ImageDataGenerator
的出色工具,可以帮助我们完成所有前面的操作。 让我们为训练和验证数据集初始化两个数据生成器:
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
中有很多可用的选项,我们只是利用了其中一些。 随时查看这个页面上的文档,以获取更详细的信息。 在我们的训练数据生成器中,我们获取原始图像,然后对它们执行几次转换以生成新图像。 其中包括:
zoom_range
参数将图像随机放大0.3
倍。rotation_range
参数将图像随机旋转50
度。width_shift_range
和height_shift_range
参数,以图像宽度或高度的0.2
因子水平或垂直地随机转换图像。shear_range
参数随机应用基于剪切的变换。horizontal_flip
参数在水平方向随机翻转一半图像。fill_mode
参数为图像填充新像素。 在这种情况下,我们只用周围最近的像素值填充新像素。让我们看看其中一些生成的图像可能看起来如何,以便您可以更好地理解它们。 我们将从训练数据集中获取两个样本图像进行说明。 第一张图片是猫的图片:
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-5Gxh9fM5-1681567233346)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/4dff20d1-9253-415f-8a18-62f217d9ddcc.png)]
让我们看一下现在是狗的图像:
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-pubJQvgN-1681567233347)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/47937375-d105-4389-89ce-20b30cfda764.png)]
请记住,对于我们的验证生成器,我们只需要将验证图像(原始图像)发送到模型以进行评估; 因此,我们仅缩放图像像素(介于 0-1 之间),并且不应用任何变换。 我们仅将图像增强转换应用于我们的训练图像:
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 模型。 我们将使用之前的相同模型架构:
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 张验证图像上验证我们的模型准确率(请记住,我们没有增加验证数据集):
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-cD9TN7of-1681567233347)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/82968e26-b880-4ddb-9fca-76ed6228eb7b.png)]
总体上,虽然验证准确率和损失存在一些峰值,但我们发现它与训练准确率非常接近,损失表明我们获得的模型与以前的模型相比,泛化效果更好。 现在保存此模型,以便稍后可以在测试数据集中对其进行评估:
model.save('cats_dogs_cnn_img_aug.h5')
现在,我们将尝试并利用迁移学习的功能,看看是否可以构建更好的模型。
到目前为止,我们已经通过指定自己的架构从头开始构建了 CNN 深度学习模型。 在本节中,我们将利用预训练的模型,该模型基本上是计算机视觉领域的专家,并且在图像分类和归类中享有盛誉。 我们建议您阅读第 4 章“迁移学习基础知识”,以简要地了解预训练模型及其在该领域中的应用。
在构建新模型或重用它们时,可以通过以下两种流行的方式来使用预训练的模型:
我们将在本节中详细介绍这两个方面。 我们将在本章中使用的预训练模型是流行的 VGG-16 模型,该模型由牛津大学的视觉几何小组创建,该模型专门为大型视觉识别构建非常深的卷积网络。 您可以在这个页面中找到有关它的更多信息。 ImageNet 大规模视觉识别挑战赛(ILSVRC)评估了用于大规模物体检测和图像分类的算法,其模型通常在这场比赛中获得第一名。
像 VGG-16 这样的预训练模型是已经在具有大量不同图像类别的巨大数据集(ImageNet)上进行训练的模型。 考虑到这一事实,正如我们之前针对 CNN 模型所学习的特征所讨论的那样,该模型应该已经学习了稳健的特征层次结构,即空间,旋转和平移不变性。 因此,该模型已经学会了对属于 1,000 个不同类别的一百万个图像的特征的良好表示,可以充当适合于计算机视觉问题的新图像的良好特征提取器。 这些新图像可能永远不会存在于 ImageNet 数据集中或可能属于完全不同的类别,但考虑到我们在第 4 章“迁移学习基础”中讨论的迁移学习原理,该模型仍应能够从这些图像中提取相关特征。
这为我们提供了一个优势,即可以使用预先训练的模型作为新图像的有效特征提取器,以解决各种复杂的计算机视觉任务,例如用较少的图像解决我们的猫对狗分类器,甚至构建狗的品种分类器,面部表情分类器 , 以及更多! 在释放迁移学习的力量解决我们的问题之前,让我们简要讨论一下 VGG-16 模型架构。
VGG-16 模型是建立在 ImageNet 数据库上的 16 层(卷积和完全连接)网络,该网络旨在进行图像识别和分类。 该模型是由 Karen Simonyan 和 Andrew Zisserman 建立的,并在他们的论文《用于大规模图像识别的超深度卷积网络》(arXiv 2014)。
我建议所有感兴趣的读者继续阅读本文中的优秀文献。 在第 3 章“了解深度学习架构”中简要提到了 VGG-16 模型,但我们将对其进行更详细的讨论,并在我们的示例中也使用它。 下图描述了 VGG-16 模型的架构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-64eyZpQP-1681567233347)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/fd570bbe-898c-40aa-b18b-a884739d3c8d.png)]
您可以清楚地看到,我们总共有 13 个卷积层,其中使用了3 x 3
卷积滤波器,以及用于下采样的最大池化层,每层中总共有两个完全连接的隐藏层,共 4,096 个单元,然后是 1,000 个单元的密集层, 其中每个单元代表 ImageNet 数据库中的图像类别之一。
我们不需要最后三层,因为我们将使用我们自己的完全连接的密集层来预测图像是狗还是猫。 我们更关注前五个块,因此我们可以利用 VGG 模型作为有效的特征提取器。 对于其中一个模型,我们将冻结所有五个卷积块以确保它们的权重在每个周期后都不会更新,从而将其用作简单的特征提取器。 对于最后一个模型,我们将对 VGG 模型进行微调,在该模型中,我们将解冻最后两个块(块 4 和块 5),以便在我们训练自己的模型时,它们的权重在每个周期(每批数据)得到更新。
在下面的框图中,我们代表了先前的架构以及将要使用的两个变体(基本特征提取器和微调),因此您可以获得更好的视觉视角:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gZKcajOz-1681567233347)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/e5207a55-3734-47f0-8c63-9ce3427c1841.png)]
因此,我们最关心的是利用 VGG-16 模型的卷积块,然后展平最终输出(来自特征图),以便我们可以将其输入到我们自己的密集层中进行分类。 本章本节中使用的所有代码都可以通过Transfer Learning.ipynb
Jupyter 笔记本在 CNN 中找到。
让我们利用 Keras,加载 VGG-16 模型并冻结卷积块,以便将其用作图像特征提取器:
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 模型的各层是否冻结,我们可以使用以下代码:
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-nWJ3QfeG-1681567233348)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/86e35b8e-9cc4-4655-8d61-5b3fefdd5d52.png)]
print("Trainable layers:", vgg_model.trainable_weights)
Trainable layers: []
从前面的输出中很明显,VGG-16 模型的所有层都是冻结的,这很好,因为我们不希望在模型训练期间改变它们的权重。 VGG-16 模型中的最后一个激活特征图(block5_pool
的输出)为我们提供了瓶颈特征,这些特征可以被展平并馈送到完全连接的深度神经网络分类器中。 以下代码片段显示了来自我们的训练数据的样本图像的瓶颈特征:
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-xEIgaeDg-1681567233348)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/b5e2f78f-af3a-4480-b5c1-745bfeea9898.png)]
我们将vgg_model
对象中的瓶颈特征展平,以使其可以被馈送到我们完全连接的分类器中。 节省模型训练时间的一种方法是使用该模型,并从我们的训练和验证数据集中提取所有特征,然后将它们作为输入提供给分类器。 现在,让我们从训练和验证集中提取瓶颈特征:
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
的扁平瓶颈特征。 现在让我们构建深度神经网络分类器的架构,它将这些特征作为输入:
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
的瓶颈特征向量用作我们分类模型的输入。 关于密集层,我们使用与以前的模型相同的架构。 让我们现在训练这个模型:
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-Ow428KCC-1681567233348)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/ae38af80-e7a5-45ff-a1b9-d7991de028b6.png)]
在第五个周期之后,模型训练与验证准确率之间存在相当大的差距,这清楚表明模型在此之后对训练数据过拟合。 但是总的来说,这似乎是迄今为止最好的模型,通过利用 VGG-16 模型作为特征提取器,我们甚至不需要使用图像增强策略就可以接近 90% 验证精度。 但是我们还没有充分利用迁移学习的全部潜力。 让我们尝试在此模型上使用我们的图像增强策略。 在此之前,我们使用以下代码将此模型保存到磁盘:
model.save('cats_dogs_tlearn_basic_cnn.h5')
我们将为之前使用的训练和验证数据集使用相同的数据生成器。 为了便于理解,构建它们的代码如下所示:
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
对象作为输入传递给我们自己的模型:
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 模型的层仍在此处冻结,我们仍将其仅用作基本特征提取器:
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-eRBKKnFl-1681567233348)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/14428a54-dea9-4ffd-9a3e-3173cedb5907.png)]
我们可以清楚地看到,训练值和验证准确率的值非常接近,并且模型也不会过拟合。 此外,我们达到 90% 的准确率,这很干净! 让我们现在将此模型保存在磁盘上,以便将来对测试数据进行评估:
model.save('cats_dogs_tlearn_img_aug_cnn.h5')
现在,我们将微调 VGG-16 模型以构建我们的最后一个分类器,我们将在此取消冻结第 4 块和第 5 块,如本节开头所述。
现在,我们将利用存储在vgg_model
变量中的 VGG-16 模型对象,解冻卷积块 4 和 5,同时保持前三个块处于冻结状态。 以下代码可帮助我们实现这一目标:
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:
[<tf.Variable 'block4_conv1/kernel:0' shape=(3, 3, 256, 512) dtype=float32_ref>, <tf.Variable 'block4_conv1/bias:0' shape=(512,) dtype=float32_ref>,
<tf.Variable 'block4_conv2/kernel:0' shape=(3, 3, 512, 512) dtype=float32_ref>, <tf.Variable 'block4_conv2/bias:0' shape=(512,) dtype=float32_ref>,
<tf.Variable 'block4_conv3/kernel:0' shape=(3, 3, 512, 512) dtype=float32_ref>, <tf.Variable 'block4_conv3/bias:0' shape=(512,) dtype=float32_ref>,
<tf.Variable 'block5_conv1/kernel:0' shape=(3, 3, 512, 512) dtype=float32_ref>, <tf.Variable 'block5_conv1/bias:0' shape=(512,) dtype=float32_ref>,
<tf.Variable 'block5_conv2/kernel:0' shape=(3, 3, 512, 512) dtype=float32_ref>, <tf.Variable 'block5_conv2/bias:0' shape=(512,) dtype=float32_ref>,
<tf.Variable 'block5_conv3/kernel:0' shape=(3, 3, 512, 512) dtype=float32_ref>, <tf.Variable 'block5_conv3/bias:0' shape=(512,) dtype=float32_ref>]
您可以从前面的输出中清楚地看到,与块 4 和 5 有关的卷积和池化层现在是可训练的,并且还可以使用以下代码来验证冻结和解冻哪些层:
layers = [(layer, layer.name, layer.trainable) for layer in vgg_model.layers] pd.DataFrame(layers, columns=['Layer Type', 'Layer
Name', 'Layer Trainable'])
前面的代码生成以下输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UOB77LQZ-1681567233349)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/42d93da8-d1e5-45c3-9c03-83ace0fcf7d7.png)]
我们可以清楚地看到最后两个块现在是可训练的,这意味着当我们传递每批数据时,这些层的权重也将在每个周期中通过反向传播进行更新。 我们将使用与之前的模型相同的数据生成器和模型架构,并对模型进行训练。 因为我们不想卡在任何局部最小值上,所以我们会稍微降低学习率,并且我们也不想突然将可训练的 VGG-16 模型层的权重突然增加可能会对模型产生不利影响的大因素:
# 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-UjOwVp5f-1681567233349)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/5c23c579-af88-4668-90a6-ae5faf899502.png)]
我们可以看到,这里的准确率值确实非常好,尽管模型看起来可能对训练数据有些过拟合,但我们仍然获得了很高的验证准确率。 现在,使用以下代码将此模型保存到磁盘:
model.save('cats_dogs_tlearn_finetune_img_aug_cnn.h5')
现在,通过在测试数据集上实际评估模型的表现,将所有模型进行测试。
现在,我们将评估到目前为止构建的五个不同模型,方法是首先在样本测试图像上对其进行测试,然后可视化 CNN 模型实际上是如何尝试从图像中分析和提取特征,最后通过在测试数据集上测试每个模型的表现来进行评估 。 如果要执行代码并遵循本章的内容,Model Performance Evaluations.ipynb
Jupyter 笔记本中提供了此部分的代码。 我们还构建了一个名为model_evaluation_utils
的实用工具模块,我们将使用该模块来评估深度学习模型的表现。 让我们在开始之前加载以下依赖项:
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
加载这些依赖关系后,让我们加载到目前为止已保存的模型:
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')
这有助于我们检索使用各种技术和架构在本章中创建的所有五个模型。
现在,我们将加载不属于任何数据集的样本图像,并尝试查看不同模型的预测。 我将在此处使用我的宠物猫的图像,因此这将很有趣! 让我们加载示例图像和一些基本配置:
# 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-uRReXOEm-1681567233349)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/04d75f26-123b-4862-ab2a-fcb28b932d36.png)]
现在,我们已经加载了示例图像,让我们看看我们的模型将其作为该图像类别的预测(我的猫):
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 模型也正确无误,并且预训练的模型具有预期的微调和图像增强。
深度学习模型通常被称为黑盒模型,因为与诸如决策树之类的简单 ML 模型相比,很难真正解释该模型在内部的工作方式。 我们知道,基于 CNN 的深度学习模型使用卷积层,该卷积层使用过滤器提取代表特征空间层次的激活特征图。 从概念上讲,顶级卷积层学习小的局部模式,而网络中较低的层则学习更复杂和更大的模式,这些模式是从顶级卷积层获得的。 让我们尝试通过一个示例来形象化。
我们将采用最佳模型(通过微调和图像增强进行迁移学习),并尝试从前八层中提取输出激活特征图。 本质上,这将最终为我们提供 VGG-16 模型前三个模块的卷积和池化层,因为我们在模型中使用了相同的特征提取。
要查看这些层,可以使用以下代码:
tl_img_aug_finetune_cnn.layers[0].layers[1:9]
[<keras.layers.convolutional.Conv2D at 0x7f514841b0b8>, <keras.layers.convolutional.Conv2D at 0x7f514841b0f0>, <keras.layers.pooling.MaxPooling2D at 0x7f5117d4bb00>, <keras.layers.convolutional.Conv2D at 0x7f5117d4bbe0>, <keras.layers.convolutional.Conv2D at 0x7f5117d4bd30>, <keras.layers.pooling.MaxPooling2D at 0x7f5117d4beb8>, <keras.layers.convolutional.Conv2D at 0x7f5117d4bf98>, <keras.layers.convolutional.Conv2D at 0x7f5117d00128>]
现在,基于它试图从我的猫的样本测试图像中提取的内容,从我们的模型中提取特征图。 为了简单说明,我们从块 1 中提取第一个卷积层之后的输出,并在以下代码段中从中查看一些激活特征图:
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-UVd7AVUN-1681567233349)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/6652c604-0dbe-4ce6-abe2-979daa464c24.png)]
从前面的输出中,我们可以清楚地看到,第一卷积层的输出为我们提供了总共 64 个激活特征图,每个特征图的大小为150 x 150
。 我们在前面的代码段中可视化了其中五个特征图。 您可以看到模型如何尝试提取与图像有关的相关特征,例如色相,强度,边缘,角等。 以下输出描绘了来自块 1 和 2 的 VGG-16 模型的更多激活图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8B7Js77J-1681567233349)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/1f407278-f56d-4d09-b2e1-ceb9ea3ad9e5.png)]
为了获得上述激活函数图,我们利用了Model Performance Evaluations.ipynb
Jupyter 笔记本中可用的代码段,这要感谢 Francois Chollet 和他的书《Python 深度学习》,它可以帮助可视化我们的 CNN 模型中所有选定的层。我们已经可视化了模型的前八层,这是我们在笔记本中较早选择的,但是在这里显示了前两个块的激活图。 随意检出笔记本并为自己的模型重复使用相同的代码。 从前面的屏幕截图中,您可能会看到顶层特征图通常保留了很多原始图像,但是当您深入模型时,特征图变得更加抽象,复杂且难以解释。
现在是时候进行最终测试了,在该测试中,我们通过对测试数据集进行预测来从字面上测试模型的表现。 在进行预测之前,让我们先加载并准备测试数据集:
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)
现在我们已经准备好按比例缩放的数据集,让我们通过对所有测试图像进行预测来评估每个模型,然后通过检查预测的准确率来评估模型的表现:
# 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-R4CXWwzg-1681567233350)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/87483eb5-1c35-4109-8e55-167fad61e513.png)]
# 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-SDyNAG7C-1681567233350)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/db437583-5d9f-45a6-8743-2f82b51f79ac.png)]
# 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-AWSr6mu8-1681567233350)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/ee23ffeb-3522-48a2-be49-14afc13b75b4.png)]
# 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-DB0TENPo-1681567233350)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/5109e69b-4a8d-42ea-995e-728f0130f11d.png)]
# 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-mT0jHSw4-1681567233351)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/713a7c6d-264b-4ba3-b7d9-6c85f31cf868.png)]
我们可以看到我们肯定有一些有趣的结果。 每个后续模型的表现均优于先前模型,这是预期的,因为我们对每个新模型都尝试了更高级的技术。 我们最差的模型是基本的 CNN 模型,其模型准确率和 F1 分数约为 78%,而我们最好的模型是经过微调的模型,其中包含迁移学习和图像增强,从而为我们提供了一个模型准确率和 96% 的 F1 得分,考虑到我们从 3,000 个图像训练数据集中训练了模型,这真是太了不起了。 现在,让我们绘制最差模型和最佳模型的 ROC 曲线:
# 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-wcYu78DE-1681567233351)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/8ccc305e-368b-48e0-a031-c076776e0c1e.png)]
这应该给您一个很好的主意,即预训练模型和迁移学习可以带来多大的差异,尤其是当我们面临诸如数据较少等约束时,在解决复杂问题上。 我们鼓励您使用自己的数据尝试类似的策略。
本章的目的是让您更深入地了解构建深度学习模型以解决实际问题,并了解迁移学习的有效性。 我们涵盖了迁移学习需求的各个方面,尤其是在解决数据受限的问题时。 我们从头开始构建了多个 CNN 模型,还看到了适当的图像增强策略的好处。 我们还研究了如何利用预训练的模型进行迁移学习,并介绍了使用它们的各种方法,包括用作特征提取器和微调。 我们看到了 VGG-16 模型的详细架构,以及如何利用该模型作为有效的图像特征提取器。 与迁移学习有关的策略(包括特征提取和微调以及图像增强)被用来构建有效的深度学习图像分类器。
最后但并非最不重要的一点是,我们在测试数据集上评估了所有模型,并获得了一些卷积神经网络在构建特征图时如何在内部可视化图像的观点。 在随后的章节中,我们将研究需要迁移学习的更复杂的实际案例研究。 敬请关注!