深度学习架构手册-全-
深度学习架构手册(全)
原文:
annas-archive.org/md5/3540d3ca64cea2cc08daad9a2559bb49译者:飞龙
前言
作为一名深度学习的从业者和爱好者,我花了多年时间从 Kaggle、GitHub、同事以及现实生活中的应用案例中学习和积累经验。我意识到,在深度学习领域,缺乏一套完整的、端到端的资源。虽然传统的大规模开放在线课程(MOOC)有帮助,但它们通常缺乏那些只能通过实际操作经验获得的实用知识和现实洞察。
为了弥补这一差距,我创建了《深度学习架构师手册》,这是一本全面且实用的指南,结合了我独特的经验和见解。本书将帮助你了解深度学习的复杂领域,为你提供那些通常需要多年实践经验才能获得的知识和见解,并将其浓缩为一个可以在几天或几周内消化的资源。
本书深入探讨了深度学习生命周期的各个阶段,从规划和数据准备到模型部署和治理。在这段旅程中,你将接触到基础和先进的深度学习架构,如多层感知机(MLPs)、卷积神经网络(CNNs)、递归神经网络(RNNs)、自编码器、变换器以及最前沿的方法,如神经架构搜索(NAS)。本书分为三部分,涵盖了基础方法、模型洞察以及 DLOps,探讨了高级话题,如 NAS、对抗性性能和大语言模型(LLM)解决方案。通过本书的学习,你将为设计、开发和部署有效的深度学习解决方案做好充分准备,释放其全部潜力,并推动各类应用的创新。
我希望这本书能作为我回馈社区的一种方式,通过激发讨论、挑战假设并启发深度学习领域的新思想和新方法。我邀请你与我一起踏上这段旅程,期待在我们共同探索深度学习这一迷人世界的过程中,听到你的想法和反馈。欢迎通过 LinkedIn 与我联系:www.linkedin.com/in/chineekin,也可以通过 Kaggle 联系我:www.kaggle.com/dicksonchin93,或者通过我 LinkedIn 个人资料上的其他渠道联系。你的独特经历和观点无疑将有助于本书的持续发展以及深度学习社区的整体进步。
本书的读者对象
本书最适合深度学习从业者、数据科学家和机器学习开发人员,他们希望探索深度学习架构来解决复杂的商业问题。本书的读者群体是那些将把知识应用到商业用例中的深度学习和 AI 领域的专业人士。为了充分利用本书内容,需要具备 Python 编程的工作知识和基本的深度学习技术理解。
本书涵盖内容
第一章,深度学习生命周期,介绍了深度学习项目的关键阶段,重点是规划和数据准备,为整本书对深度学习生命周期的全面探索奠定了基础。
第二章,设计深度学习架构,深入探讨了深度学习架构的基础,包括 MLP,并讨论了它们在先进神经网络中的作用,以及反向传播和正则化的重要性。
第三章,理解卷积神经网络,深入探讨了 CNN 及其在图像处理中的应用,以及 CNN 领域中的各种模型家族。
第四章,理解循环神经网络,探讨了 RNN 的结构和变体及其有效处理序列数据的能力。
第五章,理解自编码器,探讨了自编码器作为表示学习方法的基本原理及其在不同数据模态中的应用。
第六章,理解神经网络变换器,深入探讨了变换器的多功能性质,能够处理多种数据模态而没有明确的数据特定偏见,并讨论了它们在各种任务和领域中的潜在应用。
第七章,深度神经网络架构搜索,介绍了 NAS 的概念,作为一种自动化设计先进神经网络的方法,并讨论了其在不同场景中的应用和局限性。
第八章,探索有监督深度学习,涵盖了各种有监督学习问题类型、实现和训练深度学习模型的技术,以及使用流行的深度学习框架进行的实际实现。
第九章,探索无监督深度学习,讨论了深度学习对无监督学习的贡献,特别强调了无监督预训练方法。利用互联网上大量免费的数据,这种方法提高了下游有监督任务的模型性能,并为走向通用人工 智能(AI)铺平了道路。
第十章,探索模型评估方法,概述了模型评估技术、度量工程和优化评估度量的策略。
第十一章,解释神经网络预测,深入探讨了预测解释的领域,重点介绍了集成梯度技术及其在理解神经网络预测中的实际应用。
第十二章,解读神经网络,深入探讨了模型理解的细微差别,并展示了揭示神经元检测模式的技术。通过探索真实图像并通过优化生成图像来激活特定神经元,你将获得神经网络决策过程的宝贵见解。
第十三章,探索偏差与公平性,解决了机器学习模型中偏差与公平性的关键问题,讨论了各种类型、度量标准和程序化方法来检测和缓解偏差。
第十四章,分析对抗性性能,探讨了对抗性性能分析在识别机器学习模型漏洞和弱点中的重要性,并提供了实践示例和分析技巧。
第十五章,深度学习模型的生产部署,重点介绍了在生产环境中部署深度学习模型的关键组件、需求和策略,包括架构选择、硬件基础设施和模型包装。
第十六章,治理深度学习模型,探讨了模型治理的基本支柱,包括模型使用、模型监控和模型维护,同时提供了监控深度学习模型的实际步骤。
第十七章,在动态环境中有效管理漂移效应,讨论了漂移的概念及其对模型性能的影响,并提供了检测、量化和减轻深度学习模型漂移的策略。
第十八章,探索 DataRobot AI 平台,展示了 AI 平台,特别是 DataRobot,在简化和加速深度学习生命周期中的好处,并强调了该平台的各种功能和能力。
第十九章,构建 LLM 解决方案,深入探讨了 LLM 及其潜在应用、挑战和创建有效、具备上下文意识解决方案的策略。
为了充分利用本书
本章提供的代码已在配置为 Python 3.10、Ubuntu 20.04 LTS 64 位操作系统、32 GB 内存和 RTX 2080TI GPU 的计算机上进行测试,用于运行深度学习模型。虽然代码已在此特定配置下测试过,但也可能适用于其他配置;不过,兼容性和性能无法保证。Python 依赖项已包含在每章相应 GitHub 文件夹中的requirements.txt文件中,便于安装。此外,可能还需要一些非 Python 软件;其安装说明将在每个相关教程的开头提到。对于这些软件安装,您需要参考外部手册或指南进行安装。在进行本书中的实际代码部分时,请记住系统配置的潜在差异。
如果您使用的是本书的数字版本,建议您亲自输入代码,或从本书的 GitHub 仓库获取代码(链接将在下一部分提供)。这样可以帮助您避免与复制粘贴代码相关的潜在错误。
下载示例代码文件
您可以从 GitHub 上下载本书的示例代码文件,网址是github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook。如果代码有更新,它会在 GitHub 仓库中更新。
我们还提供了来自我们丰富书籍和视频目录的其他代码包,您可以在github.com/PacktPublishing/找到。赶紧查看吧!
使用的约定
本书中使用了多种文本约定。
Code in text:表示文本中的代码字、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 用户名。以下是一个示例:“我们将使用pandas进行数据处理和结构化,使用matplotlib和seaborn绘制图表,使用tqdm可视化迭代进度,并使用lingua进行文本语言检测。”
代码块设置如下:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from tqdm import tqdm
from lingua import Language, LanguageDetectorBuilder
tqdm.pandas()
任何命令行输入或输出如下所示:
sudo systemctl start node_exporter sudo systemctl start prometheus
粗体:表示新术语、重要词汇或屏幕上显示的词汇。例如,菜单或对话框中的文字以粗体显示。以下是一个示例:“现在我们可以通过点击左上角选项卡中的三条线按钮,然后在管理下拉菜单中点击数据源选项卡来设置 Prometheus 链接。”
小贴士或重要说明
如下所示。
联系我们
我们始终欢迎读者的反馈。
一般反馈:如果您对本书的任何部分有疑问,请通过电子邮件联系我们,邮箱地址是customercare@packtpub.com,并在邮件主题中提到书名。
勘误:虽然我们已经尽力确保内容的准确性,但错误难免发生。如果你发现书中的错误,我们将非常感激你能向我们报告。请访问www.packtpub.com/support/errata并填写表格。
盗版:如果你在互联网上遇到我们的作品的任何非法复制形式,我们将非常感激你能提供该位置或网站名称。请通过版权@packt.com与我们联系,并提供相关链接。
如果你有兴趣成为作者:如果你在某个领域具有专业知识,且有兴趣撰写或参与书籍的编写,请访问authors.packtpub.com。
分享你的想法
你的评论对我们和技术社区非常重要,将帮助我们确保提供优质的内容。
分享你的想法
一旦你阅读完《深度学习架构师手册》,我们非常希望听到你的想法!点击这里直接前往亚马逊评论页面,分享你的反馈。
你的评论对我们和技术社区非常重要,将帮助我们确保提供优质的内容。
下载这本书的免费 PDF 副本
感谢购买本书!
你喜欢在路上阅读,但又无法随身携带印刷书籍吗?
你的电子书购买是否与你选择的设备不兼容?
别担心,现在购买每本 Packt 书籍,你都会免费获得该书的无 DRM PDF 版本。
随时随地,在任何设备上阅读。直接从你喜爱的技术书籍中搜索、复制并粘贴代码到你的应用程序中。
优惠不仅仅如此,你还可以每天在邮箱中收到独家折扣、新闻通讯和精彩的免费内容。
按照以下简单步骤获取福利:
- 扫描二维码或访问以下链接

packt.link/free-ebook/9781803243795
-
提交你的购买凭证
-
就是这样!我们将直接把你的免费 PDF 和其他福利发送到你的邮箱。
第一部分 – 基础方法
本书的这一部分将帮助你全面理解深度学习架构中的基础方法和技术。从深度学习生命周期开始,你将从高层次探讨各个阶段,从规划和数据准备到模型开发、洞察、部署和治理。接着,你将深入学习设计深度学习架构的复杂性,例如 MLP、CNN、RNN、自动编码器和变压器。你还将了解神经架构搜索这一新兴方法及其对深度学习领域的影响。
在本部分中,您还将深入探讨监督式和无监督深度学习的实际应用,涵盖如二分类、多分类、回归和多任务学习等主题,以及无监督预训练和表示学习。通过聚焦于实际应用,本部分提供了关于使用流行框架和编程语言实现深度学习模型的宝贵见解。
到本部分结束时,您将对深度学习架构、方法和生命周期有坚实的基础,这将帮助您继续面对在构建深度学习解决方案过程中遇到的其他挑战。
本部分包含以下章节:
-
第一章, 深度学习生命周期
-
第二章, 设计深度学习架构
-
第三章, 理解卷积神经网络
-
第四章, 理解递归神经网络
-
第五章, 理解自编码器
-
第六章, 理解神经网络变换器
-
第七章, 深度神经架构搜索
-
第八章, 探索监督式深度学习
-
第九章, 探索无监督深度学习
第一章:深度学习生命周期
在本章中,我们将探讨深度学习生命周期的复杂性。深度学习生命周期与机器学习生命周期具有相似的特点,它既是一个框架,也是一个方法论,能够让一个深度学习项目要么取得巨大的成功,要么在适当的时候完全废弃。我们将掌握为什么这个过程是循环的,并深入理解生命周期的一些初步过程。此外,我们还将简单预览生命周期后续过程的高层次内容,这些内容将在未来的章节中深入探讨。
综合来看,本章将帮助你完成以下内容:
-
理解深度学习生命周期与机器学习生命周期之间的相似性和差异性
-
理解领域知识在深度学习项目中的作用
-
理解规划深度学习项目的几个关键步骤,确保它能够切实创造现实世界的价值
-
在高层次上掌握一些深度学习模型开发的细节
-
在高层次上掌握模型解释的重要性以及深度学习解释技术的多样性
-
探索模型部署的高层次概念及其治理
-
学会选择必要的工具来执行深度学习生命周期中的过程
我们将在以下章节中讲解这些内容:
-
机器学习生命周期
-
深度学习生命周期的构建策略
-
数据准备阶段
-
深度学习模型开发
-
提供模型洞察
-
风险管理
技术要求
本章包括一些Python编程语言的实用实现。要完成本章内容,你需要一台安装了以下库的计算机:
-
pandas -
matplotlib -
seaborn -
tqdm -
lingua
代码文件可在 GitHub 上找到:github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_1。
理解机器学习生命周期
深度学习是更广泛的机器学习类别的一个子集。它与其他机器学习算法的主要区别在于其基础构建块——神经网络。随着深度学习自 2000 年代初以来的巨大进步,它通过其机器学习对等物实现了许多以前无法完成的壮举。特别是,深度学习在识别复杂模式方面取得了突破,这些模式存在于复杂且非结构化的数据中,例如文本、图像、视频和音频。如今,深度学习的一些成功应用包括图像中的人脸识别、音频数据中的语音识别和文本数据中的语言翻译。
另一方面,机器学习是更广泛的人工智能类别的一个子集。其算法,如基于树的模型和线性模型,虽然不被视为深度学习模型,但仍然适用于涉及表格数据的广泛应用场景,而表格数据是大多数小型和大型组织所存储的数据。这些表格数据可能存在于多个结构化数据库中,数据跨度可能达到 1 到 10 年的历史数据,且这些数据具有用于构建预测性机器学习模型的潜力。一些机器学习算法的显著预测应用包括金融行业中的欺诈检测、电子商务中的产品推荐以及制造业中的预测性维护。图 1**.1 显示了深度学习、机器学习和人工智能之间的关系,以便更清晰地区分它们:

图 1.1 – 人工智能关系
现在我们大致了解了深度学习和机器学习的概念,我们准备好一窥机器学习生命周期,如图 1**.2所示:

图 1.2 – 深度学习/机器学习生命周期
深度学习算法相比其他机器学习算法尽管更加先进和复杂,但确保这两个领域成功所需的指导方法无疑是相同的。机器学习生命周期涉及六个阶段,这些阶段之间以不同的方式相互作用:
-
规划
-
数据准备
-
模型开发
-
交付 模型洞察
-
模型部署
-
模型治理
图 1**.2 展示了这六个阶段以及通过箭头表示的可能阶段转换。通常,一个机器学习项目会根据业务需求在各个阶段之间反复迭代。在深度学习项目中,大多数创新性的预测性应用场景需要手动数据收集和数据标注,这一过程属于数据准备阶段。由于这个过程通常非常耗时,尤其是当数据本身不容易获得时,一种解决方案是从一个可以接受的初始数据量开始,然后过渡到模型开发阶段,随后进入交付模型洞察阶段,以确保从想法中得到的结果是合理的。
在初步验证过程之后,依照业务需求,实践者将决定是否重新进入数据准备阶段,并根据不同数据量的里程碑,周期性地迭代这些阶段,直到模型开发和业务指标的结果都令人满意。获得必要的利益相关者批准后,项目将进入模型部署阶段,届时构建的机器学习模型将被提供,以便其预测结果得以使用。最后阶段是模型治理,在这一阶段,实践者执行管理已部署机器学习模型的风险、性能和可靠性的任务。模型部署和模型治理都值得更深入的讨论,并将在本书后期的章节中单独介绍。每当任何关键指标未能维持在某一确定的置信度水平时,项目将返回到数据准备阶段,并重新开始相同的流程。
理想的机器学习项目应根据业务需求,按周期性阶段流动。然而,机器学习项目通常容易遭遇高概率的失败。根据 Dimensional Research 和 Alegion 进行的一项调查,涵盖了来自 20 个不同业务行业的约 300 名机器学习从业者,78%的机器学习项目在部署前的某个阶段被拖延或受阻。此外,Gartner 预测,85%的机器学习项目将失败(venturebeat.com/2021/06/28/why-most-ai-implementations-fail-and-what-enterprises-can-do-to-beat-the-odds/)。通过预见意外并在它们发生之前预判失败,实践者可以在规划阶段及早避免潜在的失败因素。这也引出了图 1**.2中与垃圾图标捆绑在一起的概念。一个良好的项目,通常只有在交付模型洞察阶段被舍弃,那时已明确提出的模型和项目无法交付令人满意的结果。
现在我们已经概述了机器学习生命周期,让我们逐个阶段深入探讨,并将其分解为多个部分,帮助你发现完成每个阶段所需的关键技巧和方法。这些阶段将以抽象的方式进行讨论,并不是对你在项目中最终应采取的实际步骤的具体描述,因为每个项目都是独特的,策略应始终根据具体情况进行评估。
策划构建深度学习系统
深度学习模型只有成为执行某种操作的系统的一部分,才能实现实际的价值。将深度学习模型从研究论文带到实际的现实世界应用并非易事。因此,在进行任何项目之前进行适当的规划,是实现目标的更可靠和结构化的方式。本节将讨论一些在规划深度学习项目以走向成功时,可能会有帮助的考虑因素和策略。
启程
如今,深度学习从业者往往过于专注于模型构建过程中的算法部分。要避免被最先进(SOTA)的研究技术吸引,需要相当大的心理耐力。像pixtopix这样的疯狂技术,它能够从草图或图像掩模中生成高分辨率的真实彩色图像,和像自然语言处理(NLP)技术中的GPT-3(一个拥有 1750 亿参数的文本生成模型,由 OpenAI 开发)和 GPT-4(GPT-3 及其子模型的继任者,一个多模态文本生成模型,能够生成从文本摘要到代码生成的任何文本格式内容)一样,为什么不让它们成为主流呢?!
开个玩笑,要成为一名真正的深度学习架构师,我们需要达成共识:任何成功的机器学习或深度学习项目都是从业务问题出发的,而不是从你刚在网上读到的、附带公共 GitHub 仓库的闪亮新研究论文开始的。规划阶段通常会涉及许多对机器学习算法细节不太了解的业务高管,而且通常,同一批人根本不关心这些内容。对于专注于业务的利益相关者来说,这些算法令人望而却步,再加上人工智能技术本身采纳所面临的巨大心理障碍,这并不会让项目更容易被采纳。
评估深度学习的价值
深度学习在处理非结构化数据方面最为突出。这包括图像数据、文本数据、音频数据和视频数据。这在很大程度上得益于模型自动学习并从原始数据中提取复杂、高层次特征的能力。在图像和视频的案例中,深度学习模型能够捕捉空间和时间模式,识别物体、场景和活动。对于音频数据,深度学习能够理解语音、噪音和各种声音元素的细微差别,使得构建语音识别、语音助手和音频分类系统等应用成为可能。对于文本数据,深度学习模型能够捕捉上下文、语义和语法,从而支持如情感分析、机器翻译和文本摘要等自然语言处理(NLP)任务。
这意味着,如果您的公司在其业务流程中存在并利用这些数据,可能有机会借助深度学习来解决一个问题。然而,切勿仅仅为了使用深度学习而过度复杂化问题。用一个更容易理解的比喻来说,你不会用一把巨大的大锤去把钉子钉进木头。虽然可能有效,而且你或许能成功,但你可能会把钉子弄弯,甚至可能在使用过程中伤到自己。
一旦识别出一个问题,就要评估解决它的商业价值。并非所有问题都是一样的,它们可以根据业务影响、价值、复杂度、风险、成本和深度学习的适用性进行排序。通常,你应该寻找高影响、高价值、低复杂度、低风险、低成本并且适合深度学习的问题。虽然这些指标之间存在权衡,但简而言之,要确保你发现的问题值得用深度学习来解决。一个普遍的经验法则是,面对问题时,总是优先选择更简单的解决方案,即使这意味着放弃使用深度学习技术。简单的方法往往更可靠、成本更低、风险更小,且更快见效。
考虑一个问题,在视频流中需要移除背景场景,只保留人类或必要的物体不受影响,以便可以将更合适的背景场景叠加为背景。这是当今所有电影类型中,专业电影制作行业常见的问题。
语义分割是指在图像的宽度和高度维度上为每个像素分配标签的任务,这是一种解决此类问题所需的方法。在这种情况下,任务需要分配标签,以帮助识别哪些像素需要被移除。随着许多公开可用的语义分割数据集的出现,深度学习在语义分割领域取得了显著进展,使其能够实现对世界的非常满意的细粒度理解,足以广泛应用于自动驾驶和机器人导航等行业。然而,深度学习并不以 100%无误著称,几乎总是存在一些误差,即使在受控的评估数据集上也是如此。例如,在人体分割的情况下,模型可能会在细小的发丝区域产生最多的错误。大多数电影制作人追求电影的完美呈现,并要求每一个像素都被恰当且无误地移除,因为电影演员的时间非常昂贵。此外,若场景没有使用绿幕拍摄,那么手动去除物体将浪费大量时间和金钱,而这些物体本可以通过绿幕简单去除。这是一个不应过度复杂化问题的例子。要解决上述问题,你只需要一个绿幕:具体来说,罕见的色度键绿色。当绿幕在需要数字化叠加图像的区域妥善准备时,图像处理技术本身可以去除被认为属于色度键绿色范围内的小光强度范围的像素,并通过基于规则的解决方案有效地实现语义分割。绿幕是一种更简单、经济、万无一失且快速设置的解决方案。
这真是一口气说完!现在,我们来看看一个更简单的问题。假设我们需要自动化地识别下雨的情况。在这个应用场景中,理解识别下雨的实际需求和目标是非常重要的:仅仅在下雨发生的瞬间检测到雨是否足够吗?还是我们需要识别雨是否会在不久的未来发生?我们将如何使用降雨事件的信息?这些问题将帮助我们判断是否需要使用深度学习。我们作为人类,知道通过视觉输入可以预测雨,比如通过观察降水的存在或者云层的状况。然而,如果应用场景仅仅需要在下雨时进行检测,并且识别下雨的目标是决定何时给植物浇水,那么一个更简单的方法就是使用电子传感器来检测水的存在或湿度。只有当你想预测未来是否会下雨,比如说在 15 分钟内,深度学习才更有意义,因为有很多气象因素之间的相互作用可能会影响降雨情况。只有通过对每个应用场景进行头脑风暴,分析所有潜在的解决方案,甚至是深度学习之外的方案,才能确保深度学习相较于其他解决方案带来切实的商业价值。不要因为想用深度学习就随便应用它。
有时,当你直接考虑某个应用场景时,价值不清晰,或者即使价值很明确,但你却不知道如何执行时,可以考虑寻找同行业公司的一些参考项目。同一行业的公司通常会有很高的可能性优化相同的流程或解决相同的痛点。类似的参考项目可以作为设计深度学习系统的指南,并且能够证明正在考虑的应用场景值得使用深度学习技术。当然,并不是每个人都能获得这种详细信息,但你会惊讶于谷歌现在能告诉你什么。即使没有类似的项目可以作为直接参考,你仍然有可能从其他已经在同一行业内带来价值的机器学习项目中获得灵感。
不得不承认,有时拒绝深度学习确实是一个难以接受的决定,因为大多数从业者都是靠实现深度学习解决方案来获得报酬的。然而,及早放弃深度学习将使你能将时间集中在更有价值的问题上,这些问题更适合通过深度学习来解决,并且能够避免在某些情况下,简单的解决方案胜过深度学习时,削弱深度学习潜力的风险。深度学习是否值得采用的标准应当根据具体情况进行评估,作为一名从业者,最好的建议是遵循常识。花足够的时间进行问题探索和价值评估。你最不想发生的事情,就是花费大量时间准备数据、构建深度学习模型,并提供非常有说服力的模型见解,结果却发现你试图预测的标签对于业务没有足够的价值,无法进一步投资。
定义成功
曾经听过类似这样的句子吗:“我的深度学习模型在验证数据集上达到了 99%的准确率!”?数据科学家经常犯一个错误,那就是仅仅通过验证指标来判断一个机器学习项目的成功,这些验证指标通常是在模型开发过程中用于评估机器学习模型的表现。像准确率、精确度或召回率这样的模型构建指标在机器学习项目中确实很重要,但除非这些指标能够为业务带来价值,并以某种方式与业务目标相连接,否则它们往往毫无意义。一个项目可能取得了不错的准确率,但仍然未能实现预期的业务目标。这种情况通常出现在没有在早期定义出合适的成功指标,从而导致在数据准备和模型开发阶段使用了错误的标签。此外,即使模型指标对业务流程产生了积极影响,也有可能无法有效地传达给业务相关方,最糟糕的情况是,当报告按原样呈现时,模型的成功并未得到认可。
成功指标在早期定义时,充当着机器学习项目的保护线,确保项目目标与业务目标一致。保护线之一是,成功指标可以帮助指导选择一个适当的标签,该标签在推理时能切实改善业务流程,或以其他方式为业务创造价值。首先,让我们确保我们对标签的含义有统一的理解,标签是你希望机器学习模型预测的一个值。机器学习模型的目的是根据某些形式的输入数据自动分配这些标签,因此在数据准备和模型开发阶段,需要选择一个标签来服务于这一目的。选择错误的标签可能会对深度学习项目造成灾难性后果,因为有时候,当数据无法直接获取时,意味着项目必须从数据准备阶段重新开始。标签应始终间接或直接与成功指标相关联。
成功指标,顾名思义,可以是复数,范围从基于时间的成功定义或里程碑到整体项目成功,从无形到有形。最佳实践是通常进行头脑风暴并记录所有可能的成功标准,从低层次到高层次。另一个最佳实践是确保始终与无形指标一起定义有形成功指标。无形指标能产生意识,但有形指标确保事物是可衡量的,从而使其更加可达。以下是一些无形且难以衡量的指标示例:
-
提高客户满意度
-
提高员工表现
-
改善股东前景
指标是衡量事物的方式,并与目标紧密相关,以确保交易的达成。目标本身可以是无形的,类似于前面列出的几个示例,但只要它与有形指标挂钩,项目就有了良好的开端。当你有了明确的目标时,问问自己,如何证明目标已经实现、展示或衡量。以下是一些可能与业务目标对齐的机器学习项目的有形成功指标示例:
-
增加客户的停留时间,这可以作为客户满意度的一个代理指标
-
增加公司收入,这可以作为员工表现的一个代理指标
-
增加点击率(CTR),这可以作为定向营销活动有效性的一个代理指标
-
增加客户生命周期价值(CLTV),这可以作为长期客户满意度和忠诚度的一个代理指标
-
提高转化率,这可以作为促销活动和网站用户体验成功的一个代理指标
这个概念并不新颖,也不仅限于机器学习项目——几乎每个为公司进行的项目都需要与业务目标对齐。许多基础的项目管理技术同样适用于机器学习项目,花时间学习一些项目管理技能对机器学习项目来说是有益的,并且这些技能可以迁移到机器学习项目中。此外,由于机器学习被认为是一项基于软件的技术,软件项目管理方法论同样适用。
最后需要记住的一点是,机器学习系统不仅仅关乎你的机器学习模型有多先进,而是关于人类与机器智能如何协作,以实现更大的目标并创造价值。
规划资源
深度学习通常涉及拥有大量参数(即权重)的神经网络架构。这些架构的大小可以从仅包含几个参数到包含数百亿个参数。例如,OpenAI 的 GPT-3 文本生成模型包含 1750 亿个神经网络参数,存储大小约为 350 GB。这意味着,要运行 GPT-3,你需要一台至少有 350 GB 随机存取内存(RAM)的机器!
深度学习模型框架,如 PyTorch 和 TensorFlow,已经被构建为可以与被称为图形处理单元(GPU)的设备一起使用,这些设备能提供巨大的神经网络模型训练和推理加速。市面上的 GPU 设备通常配备 12 GB 的 GPU 内存,远远不足以在 GPU 模式下加载 GPT-3 模型。然而,仍然有方法将大型模型划分到多个 GPU 上,并在 GPU 上运行该模型。此外,某些方法还可以支持分布式 GPU 模型训练和推理,以支持在任何一个使用点更大的数据批量大小。GPU 并不是便宜的设备,最广泛使用的 GPU 品牌 Nvidia 的价格从几百美元到数十万美元不等。随着加密货币技术的兴起,GPU 的可用性显著下降,因为人们一有库存就会立即购买它们。所有这些都突显了在深度学习模型训练和推理之前规划计算资源的重要性。
在项目初期,将你的模型开发和部署需求与计算资源分配对齐是非常重要的。首先,通过浏览研究论文或提供技术总结的优质网站,评估适合当前任务的深度学习架构的规模范围,并为模型开发过程预留计算资源。
提示
paperswithcode.com 提供了按各种任务分类的广泛技术总结!
当计算资源不可随时使用时,确保你总是提前制定购买计划,尤其是涉及到 GPU 时。但如果不想使用物理机器怎么办?使用计算资源的另一种选择是使用付费云计算资源提供商,您可以轻松地在世界任何地方在线访问。在模型开发阶段,分配更多 GPU 和更大 RAM 的一个好处是,可以通过在训练过程中使用更大的数据批次,或者允许同时训练多个模型,从而加速模型训练。通常也可以使用仅 CPU 进行深度学习模型训练,但模型训练时间会不可避免地更长。
在训练过程中所需的基于 GPU 和 CPU 的计算资源,通常在推理时使用时被认为是过剩的。不同的应用程序有不同的部署计算需求,决定分配哪些资源规格可以通过以下三个问题来评估:
-
推理请求的频率是多少?
- 短时间内的多个推理请求可能意味着需要在多个计算设备上并行运行多个推理服务
-
每次请求预测时,平均请求的样本数量是多少?
- 设备的 RAM 需求应与批处理大小预期相匹配
-
你需要多快的回复?
-
如果响应时间要求为秒级或更快,则需要 GPU
-
如果你不在乎响应时间,CPU 也能完成任务
-
资源规划不仅仅局限于计算资源规划,还扩展到人力资源规划。假设团队中有多少名深度学习工程师和数据科学家共同工作,最终将影响模型开发过程中所使用的软件库和工具的选择。选择这些工具的类比将在未来的章节中介绍。
下一步是准备数据。
数据准备
数据对于机器学习模型,就像燃料对于汽车、电力对于电子设备、食物对于身体一样。机器学习模型通过尝试捕捉提供的输入数据与输出数据之间的关系来工作。类似于人脑的工作方式,机器学习模型将尝试遍历收集到的数据示例,并慢慢建立记忆,以便将提供的输入数据映射到提供的目标输出数据。数据准备阶段包括为构建机器学习模型准备好可以直接使用的数据的方法和过程,其中包括以下内容:
-
原始输入和目标输出数据的获取
-
获取数据的探索性数据分析
-
数据预处理
我们将在以下小节中讨论这些话题。
深度学习问题类型
深度学习可以大致分为两类问题类型,即有监督学习和无监督学习。这两种问题类型都涉及构建一个能够根据明确的数据输入做出有根据的预测输出的深度学习模型。
有监督学习是一种涉及标签的问题类型,标签作为学习的真实来源。标签可以有多种形式,可以细分为两种问题类型,即分类和回归。分类是指在给定输入数据时,预测特定离散类别的问题。许多更复杂的问题来自于基础的分类问题类型,如实例分割、多标签分类和目标检测。而回归则是指在给定输入数据时,预测连续数值的问题。同样,复杂问题类型也可以从基础的回归问题类型中派生出来,如多重回归和图像边界框回归。
另一方面,无监督学习是一种不涉及标签的问题类型,其目标可以有很大的差异。异常检测、聚类和特征表示学习是最常见的属于无监督学习类别的问题类型。
我们将在第八章《探索有监督深度学习》和第九章《探索无监督深度学习》中分别讲解这两种问题类型。
接下来,让我们了解在获取数据时应该考虑的事项。
获取数据
在深度学习的背景下,获取数据通常涉及非结构化数据,包括图像数据、视频数据、文本数据和音频数据。有时候,数据可以通过某些业务流程在数据库中预先存储并随时使用,但更多时候,它必须从头开始从环境中手动收集。此外,通常这些数据的标签并不容易获得,需要手动标注。随着深度学习算法能够处理和消化高度复杂数据的能力,它也需要比传统机器学习方法更多的数据。执行大量数据收集和数据标注的需求是深度学习今天被认为具有高门槛的主要原因。
在机器学习项目中,不要急于快速选择一个算法。花一些时间正式定义可以用来预测目标变量的特征。在这个过程中,寻求领域专家的帮助,并集思广益,寻找与目标变量相关的潜在预测特征。在实际项目中,通常会花费大量时间规划和获取数据,同时确保获得的数据适合机器学习模型使用,之后才会将剩余时间用于模型构建、部署和治理。虽然关于如何处理模型开发阶段的低质量数据已经进行了大量研究,但大多数技术并不全面,且在一定程度上只能掩盖数据固有的质量问题。如果在数据采集阶段对质量保证表现出无知,只在数据科学部分表现出热情,那么项目从一开始就注定会失败。
制定数据采集策略是一项艰巨的任务,特别是当你不知道什么才算是高质量数据时。让我们来看一下,在实际商业应用和机器学习的背景下,你应该考虑的一些数据质量的关键要素:
-
代表性:数据在多大程度上能代表真实世界数据集?
-
一致性:注释方法有多一致?相同的模式是否匹配相同的标签,还是存在一些不一致之处?
-
全面性:收集的数据集中是否涵盖了特定标签的所有变体?
-
独特性:数据中是否包含大量重复或相似的数据?
-
公平性:收集的数据是否对某些特定标签或数据群体存在偏见?
-
有效性:数据中是否包含无效字段?数据输入是否与其标签匹配?是否存在缺失数据?
让我们详细看看这些要素。
代表性
数据应尽可能模仿在模型部署期间将接收到的数据方式进行收集。在基于研究的深度学习项目中,研究人员通常会在一个封闭的环境中收集数据,并控制环境变量。研究人员偏好从控制环境中收集数据的原因之一是他们可以建立更稳定的机器学习模型,并通常试图证明某个观点。最终,当研究论文发布时,你会看到使用精心挑选的数据得出的惊人结果,旨在给人留下深刻印象。这些基于控制数据建立的模型,在应用到随机的、无法控制的现实世界示例时,往往会惨遭失败。别误会——拥有这些控制数据集在某些时候有助于建立更稳定的机器学习模型,但将无法控制的现实世界示例作为训练和评估数据集的主要部分,是实现通用模型的关键。
有时,获取的训练数据有保质期,并不会永远保持代表性。这个场景被称为数据漂移,将在本章末尾的管理风险部分中详细讨论。数据质量的代表性指标还应根据模型在部署期间将接收的数据的未来预期进行评估。
一致性
标注不一致的数据标签使得机器学习模型更难从中学习。当多个标注者之间的领域理念和标注策略存在差异且没有正确定义时,就会发生这种情况。例如,“Regular”和“Normal”意思相同,但对机器而言,它们是两个完全不同的类别;同样,“Normal”和“normal”仅有大小写差异!
在执行实际标注过程之前,练习在规划阶段制定合适的标签标注策略。对于一些简单的一致性错误,在数据标注后可以进行清理,但有些一致性错误可能难以检测且复杂难以修正。
全面性
机器学习致力于建立一个对任何特定标签的多种变体和视角都能保持稳健的决策机制。具备这一能力和成功实现它是两回事。决策稳健性的先决条件之一是用于训练和评估的数据本身必须足够全面,以涵盖每个标签的所有可能变体。如何判断全面性呢?这取决于标签的复杂性以及它们在模型部署时自然呈现的变体有多大。更复杂的标签自然需要更多的样本,而较简单的标签则需要更少的样本。
在深度学习的背景下,一个好的起点是每个标签至少拥有 100 个样本,并进行模型构建和推导模型见解的实验,以查看是否有足够的样本使模型能够对标签的未见变体进行泛化。当模型未能产生令人信服的结果时,您需要回到数据准备阶段,收集更多特定标签的数据变体。机器学习生命周期本质上是一个循环过程,您将在不同阶段之间实验、探索并验证,以获得解决问题所需的答案,因此不要害怕循环执行这些不同的阶段。
独特性
虽然拥有完整和全面的数据有助于构建一个能应对数据变化的稳健机器学习模型,但在获取的数据集中重复相同数据变体的版本则有可能导致模型产生偏见。一个有偏见的模型会做出偏颇的决策,这些决策可能不道德、违法,甚至有时完全没有意义。此外,任何特定标签的获取数据量在所有数据都重复或非常相似时变得毫无意义。
机器学习模型通常是在获取的部分数据上进行训练,然后在其他数据子集上进行评估,以验证模型在未见数据上的表现。当获取的数据集中的非独特部分偶然被放入评估分区时,模型可能会报告对重复数据输入存在偏见的评分。
公平性
获取的数据集是否适当地代表了少数群体?该数据集是否对人口中的多数群体存在偏向?机器学习模型出现偏见的原因可能有很多,但其中一个主要原因是数据表示偏见。确保数据公平、公正地表示是所有机器学习从业者的道德责任。偏见的类型有很多,因此这个话题将有自己的一章,并将在第十三章《探索偏见与公平》中介绍,偏见 和公平的缓解方法。
有效性
数据集中是否存在异常值?数据集中是否缺失数据?您是否不小心将一个空白的音频或图像文件添加到了正确收集和标注的数据集中?数据输入的标注标签是否被认为是有效标签?这些是考虑数据集有效性时应该问的一些问题。
无效数据对机器学习模型来说是无用的,其中一些会使所需的预处理变得复杂。无效性的原因可以从简单的人工错误到复杂的领域知识错误不等。减少无效数据的一种方法是将已验证的数据和未验证的数据分开。在数据样本进入已验证数据集类别之前,包含某种形式的自动化或人工数据验证过程。这些验证逻辑可以来源于业务流程或常识。例如,如果我们把年龄作为输入数据,存在可接受的年龄范围,也有一些完全不可能的年龄范围,如 1000 岁。设立简单的防护措施并在收集这些数据时验证这些值,可以立即纠正它们,从而获得准确且有效的数据。否则,在模型构建阶段,这些数据很可能会被丢弃。维护一个结构化的框架来验证数据可以确保大多数数据保持相关且可供机器学习模型使用,并避免简单的错误。
对于更复杂的无效数据,例如领域意识形态中的错误,领域专家在确保数据保持合理和逻辑性方面发挥着重要作用。在讨论如何收集和标注数据以进行模型开发时,始终确保包括领域专家来定义数据的输入和输出。
通过探索性数据分析(EDA)理解数据
数据获取后,分析数据以检查其特征、存在的模式以及数据的一般质量是至关重要的。了解你处理的数据类型可以帮助你为后续的模型构建阶段规划策略。绘制分布图、计算统计数据,并进行单变量和多变量分析,以理解数据之间的内在关系,这可以进一步确保数据的有效性。不同变量类型的分析方法不同,并且可能需要事先具备某种领域知识。在以下子部分中,我们将通过探索性数据分析(EDA)实际处理基于文本的数据,以了解进行 EDA 任务的好处。
实际文本 EDA
在本节中,我们将使用 Python 代码手动探索和分析一个特定于文本的数据集,目的是在本书的后续部分使用相同的数据集构建深度学习模型。我们使用的数据集将基于印度电商网站上商品的文本描述预测商品的类别。这个用例将有助于自动将广告商品进行分组,以供用户推荐使用,并可以帮助提高电商网站的购买量:
-
我们先定义在笔记本中将使用的库。我们将使用
pandas进行数据处理和结构化,matplotlib和seaborn用于绘制图表,tqdm用于可视化迭代进度,lingua用于文本语言检测:import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from tqdm import tqdm from lingua import Language, LanguageDetectorBuilder tqdm.pandas() -
接下来,让我们使用
pandas加载文本数据集:dataset = pd.read_csv('ecommerceDataset.csv') -
pandas有一些方便的函数来可视化和描述加载的数据集;我们来使用它们。我们从可视化原始数据的三行开始:dataset.head(3)这将在你的笔记本中显示以下图形:

图 1.3 – 可视化文本数据集样本
-
接下来,让我们通过可视化数据列的统计信息来描述数据集:
dataset.describe()这将在你的笔记本中显示以下图形:

图 1.4 – 显示数据集的统计信息
-
通过这些可视化,可以明显看出,数据集的描述与数据集中的内容一致,其中category列包含四个独特的类别,与名为description的文本描述数据列配对,且有证据表明它们是字符串。从描述函数中获得的一个重要见解是,文本描述中存在重复项。我们可以通过取所有重复项中的第一个示例行来删除重复项,但我们还必须确保重复项属于相同类别,因此我们来做一下:
for i in tqdm(range(len(unique_description_information))): assert( len( dataset[ dataset['description'] == unique_description_information.keys()[i] ]['category'].unique() ) == 1 ) dataset.drop_duplicates(subset=['description'], inplace=True) -
让我们检查一下各列的数据类型:
dataset.dtypes这将在你的笔记本中显示以下图形:

图 1.5 – 显示数据集各列的数据类型
-
当某些样本本身不是字符串数据类型时,比如空数据或数字数据,pandas 会自动使用
Object数据类型,这会将整个列分类为 pandas 无法识别的数据类型。让我们检查空值:dataset.isnull().sum()这将给我们以下输出:

图 1.6 – 检查空值
-
看起来描述列有一个空值,正如预期的那样。这可能是获取数据时的错误,或者它可能确实为空。无论如何,让我们删除这一行,因为我们无法恢复它,并将列转换为字符串:
dataset.dropna(inplace=True) for column in ['category', 'description']: dataset[column] = dataset[column].astype("string") -
之前,我们发现了四个独特的类别。让我们通过可视化其分布来确保每个类别都有足够的样本:
sns.countplot(x="category", data=dataset)这将显示以下图形:

图 1.7 – 显示类别分布的图表
每个类别都有相当数量的数据样本,并且看起来没有异常类别。
-
这里的目标是通过印度电子商务网站上的商品描述预测商品类别。从这个背景来看,我们知道印度公民讲印地语,因此数据集可能不仅包含英语数据。让我们尝试使用一个名为 Lingua 的开源语言检测工具来估计和验证数据集中可用的语言。Lingua 结合了基于规则的方法和基于机器学习模型的方法,能够检测 70 多种文本语言,非常适合短语、单词和句子的检测。由于这个原因,Lingua 在运行时和内存性能上表现更佳。让我们从
lingua库初始化语言检测实例开始:detector = LanguageDetectorBuilder.from_all_languages( ).with_preloaded_language_models().build() -
现在,我们将随机抽取数据集的一小部分进行语言检测,因为检测算法需要时间完成。使用数据的 10%作为样本应能充分理解数据:
sampled_dataset = dataset.sample(frac=0.1, random_state=1234) sampled_dataset['language'] = sampled_dataset[ 'description' ].progress_apply(lambda x: detector.detect_language_of(x)) -
现在,让我们可视化语言的分布:
sampled_dataset['language'].value_counts().plot(kind='bar')这将显示以下图形:

图 1.8 – 文本语言分布
-
有趣的是,Lingua 检测到了一些异常样本,它们不是英语。异常语言看起来可能是 Lingua 检测错误的结果。印地语也被检测到了,这比其他语言更有说服力,因为数据来自印度的电子商务网站。让我们来看一下这些样本:
sampled_dataset[ sampled_dataset['language'] == Language.HINDI ].description.iloc[0]这将显示以下文本:

图 1.9 – 可视化印地语文本
-
看起来这里混合了印地语和英语。那其他语言呢,比如法语?
sampled_dataset[ sampled_dataset['language'] == Language.FRENCH ].description.iloc[0]这将显示以下文本:

图 1.10 – 可视化法语文本
-
看起来potpourri是这里的重点词,因为这是一个借用的法语词汇,但文本总体上还是英语。
-
由于语言列表中不包括不使用空格作为逻辑词单元分隔符的语言,我们尝试通过基于空格的词分隔来衡量词语的分布。词计数和字符计数会影响深度学习神经网络的参数,因此在 EDA 过程中理解这些值会非常有用:
dataset['word_count'] = dataset['description'].apply( lambda x: len(x.split()) ) plt.figure(figsize=(15,4)) sns.histplot(data=dataset, x="word_count", bins=10)这将显示以下条形图:

图 1.11 – 词计数分布
通过对文本数据的探索和分析,我们可以推测出几个原因,这些原因有助于在模型开发阶段确定我们应该使用的模型类型和结构:
-
标签样本采样得当,每个标签有 5,000-11,000 个样本,这使得它们适用于深度学习算法。
-
原始数据不干净,存在缺失数据和重复项,但可以通过手动处理修复。直接使用原始数据进行模型开发可能会导致模型产生偏差。
-
数据集包含多种语言,但主要是英文文本;这将帮助我们在模型开发阶段做出合适的模型选择。
-
大多数样本少于 1,000 个单词,一些样本有 1,000-8,000 个单词。在一些非关键性应用场景中,我们可以安全地将单词数限制在大约 1,000 个单词,以便我们可以构建一个具有更好内存和运行时性能的模型。
前面的实际示例应该提供了一个简单的 EDA 体验,足以让你理解在进入模型开发阶段之前执行深入 EDA 的好处和重要性。与实际文本 EDA 类似,我们在 Packt GitHub 仓库中为其他数据集准备了一个实际的 EDA 样本工作流,包括音频、图像和视频数据集,你可以探索这些内容,亲自动手实践。
本节中需要掌握的一个主要概念是 EDA 的重要性,以及你应该展现出多大的好奇心来揭示数据的真相。一些方法可以推广到其他类似的数据集,但将任何特定的 EDA 工作流视为灵丹妙药,会使你忽视人们在这一领域不断贡献的研究。每当你怀疑数据中存在某些问题时,就要对数据提出疑问,并尽可能通过手动或自动检查来揭示答案。在获得这些答案时要富有创造力,并保持对学习新方法的渴望,探索如何在数据中发现关键信息。
在本节中,我们已通过方法论和实践步骤讲解了不同类型数据的 EDA 过程。接下来,我们将探讨为实际模型使用准备数据需要做什么。
数据预处理
数据预处理包括数据清理、数据结构化和数据转换,以便深度学习模型能够使用处理后的数据进行模型训练、评估和部署推理。处理后的数据不仅仅是为了让机器学习模型接受,还应该以一种优化学习潜力并提高机器学习模型指标性能的方式进行处理。
数据清理是一个旨在提高数据质量的过程。EDA 过程是识别数据集问题的前提,只有在进行数据清理之前,才能发现数据中的任何问题。数据清理和 EDA 通常是迭代执行的,直到达到令人满意的数据质量水平。清理可能很简单,比如删除重复值、删除空值或删除不合逻辑的值,无论是从常识角度还是从业务逻辑角度来看。这些都是我们之前解释过的概念,同样的风险和问题也适用。
数据结构化是一个协调数据摄取和加载过程的过程,涉及从存储的数据中加载已经清理并验证过质量的数据。这个过程决定了数据应如何从一个或多个源加载,并输入到深度学习模型中。听起来很简单,对吧?如果这是一个小型的单一 CSV 数据集,可能不会出现性能或内存问题,这会非常简单。然而,实际上,在数据可能因存储限制而被分割并存储在不同源的情况下,这个过程可能非常复杂。以下是一些你需要在此过程中考虑的具体因素:
-
你的计算机有足够的内存来处理你所需的批次大小,以供模型使用吗?确保同时考虑模型大小,以免出现内存过载和内存溢出(OOM)错误!
-
你的数据来自不同的来源吗?确保你拥有访问这些数据源的权限。
-
访问这些源时的速度延迟可接受吗?考虑将数据转移到一个更好的硬件资源上,这样你就能以更高的速度访问,例如使用固态硬盘(SSD)代替硬盘驱动器(HDD),并从远程网络可访问的源转移到直接的本地硬件源。
-
你甚至有足够的本地存储空间来存储这些数据吗?确保你有足够的存储空间来保存这些数据,不要超载存储空间,以免影响性能,甚至导致计算机崩溃。
-
优化数据加载和处理过程,使其更加高效。存储和缓存固定的数据处理输出,这样你可以节省时间,避免重新计算这些输出。
-
确保数据结构化过程是确定性的,即使有些过程需要随机性。随机确定性指的是当随机性在循环重复时可以被复现。确定性有助于确保已获得的结果可以被复现,并确保模型构建方法可以公正且可靠地进行比较。
-
记录数据,以便在需要时调试过程。
-
数据划分方法。确保选择适合你数据集的正确交叉验证策略。如果包含时间特征,考虑是否应该构建基于时间的划分方法,其中训练数据来自早期时间的示例,而评估数据则来自未来。如果没有,分层划分方法将是最佳选择。
不同的深度学习框架,例如基于 TensorFlow 的keras,基于 PyTorch 的Catalyst,基于 PyTorch 的fast ai,基于 PyTorch 的pytorch lightning,以及基于 PyTorch 的ignite。
最后,数据转换是一个应用独特数据变量特定预处理的过程,用以将原始清理后的数据转化为更具表现力、可用和可学习的格式。在执行数据结构化和转换过程时,一个重要的因素是你打算使用的深度学习模型类型。任何形式的数据转换通常依赖于深度学习架构,并且依赖于它能接受的输入类型。最广为人知和常见的深度学习模型架构是为了解决特定的数据类型问题而发明的,例如卷积神经网络用于图像数据,变换器模型用于基于序列的数据,基础的多层感知器用于表格数据。然而,深度学习模型被认为是灵活的算法,可以适应不同形式和大小的数据,即使在多模态数据条件下也能处理。通过过去几年与领域专家的合作,深度学习专家已经能够构建出能够处理多种数据模态甚至多个非结构化数据模态的创意深度学习架构,这些架构在学习跨模态模式方面取得了成功。以下是两个例子:
-
强健的自监督音视频语音识别,由 Meta AI(前 Facebook)发布(
arxiv.org/pdf/2201.01763v2.pdf)-
这解决了在多重发言的情况下进行语音识别的问题,构建了一个基于深度学习变换器的模型,可以同时接受音频和视觉数据,称为 AV-HuBERT。
-
视觉数据作为补充数据,帮助深度学习模型分辨应该关注哪个说话者。
-
它在 LRS3-TED 视觉和音频唇语识别数据集上达到了最新的最先进的结果
-
-
通过简单的序列到序列学习框架统一架构、任务和模态,由达摩院和阿里巴巴集团发布(
arxiv.org/pdf/2202.03052v1.pdf)-
他们构建了一个模型,该模型可以处理文本和图像数据,并发布了一个预训练模型
-
在 COCO 字幕数据集上完成了图像标注任务,并达到了最先进的结果
-
也就是说,数据转换主要分为两部分:特征工程和数据缩放。深度学习以其特征工程能力而闻名,特征工程替代了从原始数据中手动构建定制特征的需求。然而,这并不意味着始终没有进行特征工程的意义。许多成功的深度学习模型利用了经过工程处理的特征作为输入。
既然我们知道了数据预处理的内容,让我们理论上和实践上讨论和探索不同的非结构化数据预处理技术。
文本数据预处理
文本数据可以是不同语言的,并存在于不同领域,从描述性数据到信息文档和自然人类文本评论。以下是一些最常用的文本数据预处理方法,它们被广泛应用于深度学习:
-
词干提取:一种去除单词后缀的过程,试图将单词还原为其基础形式。这有助于促进不同形式的同一单词的特征跨使用。
-
词形还原:一种将单词简化为其基础形式的过程,从而生成真实的英语单词。词形还原具有与词干提取相似的许多好处,但由于其生成的语言学有效的单词简化输出,被认为是更好的选择。
-
文本标记化,通过字节对编码(BPE):标记化是将文本分割成不同部分的过程,这些部分将被编码并供深度学习模型使用。BPE 是一种基于子词的文本分割算法,它允许常见词汇作为单个标记输出,而稀有词汇则被分割成多个标记。这些分割后的标记可以复用来自匹配子词的表示。这样做是为了减少同时存在的词汇量,减少超出词汇表范围的标记,并使标记表示的学习更加高效。
一种不常见的预处理方法,但对于构建更具泛化能力的文本深度学习模型非常有用的是文本数据增强。文本数据增强可以通过几种方式进行:
-
用同义词替换动词:这可以通过使用来自 NLTK 库的 WordNet 英语词汇数据库中的同义词词典集来完成。获得的增强文本将通过动词同义词替换保持相同的含义。
-
回译:这涉及将文本翻译成另一种语言,再翻译回原始语言,使用像 Google 这样的翻译服务或开源的翻译模型。获得的回译文本将会以略微不同的形式呈现。
音频数据预处理
音频数据本质上是基于序列的数据,在某些情况下,可能存在多个序列。音频最常用的预处理方法之一是将原始音频数据转换为不同形式的频谱图,使用短时傅里叶变换(STFT),这是一种将音频从时域转换为频域的过程。频谱图音频转换使得音频数据能够在多个频率范围内被分解和表示,而不是像单一波形那样表示所有音频频率的信号组合。这些频谱图是二维数据,因此可以作为图像处理,并输入卷积神经网络。数据缩放方法,如对数缩放和对数梅尔缩放,通常也会应用于这些频谱图,以进一步强调频率特征。
图像数据预处理
图像数据增强是一种基于图像的特征工程技术,能够增加原始数据的全面性潜力。应用此技术的最佳实践是构建数据流水线,在训练过程中按批次随机应用图像增强,而不是为深度学习模型提供固定的增强数据集。选择图像增强类型需要理解使用案例的业务需求。以下是一些不适合应用某些增强方法的示例:
-
当图像的方向影响目标标签的有效性时,方向修改类的数据增强方法,如旋转和图像翻转,不适用
-
当图像的颜色影响目标标签的有效性时,颜色修改类的数据增强方法,如灰度、通道混洗、色调饱和度变化和 RGB 偏移并不适用
在排除显然不适用的数据增强方法后,找出最佳增强方法列表的一个常见但有效的方法是通过迭代实验和模型比较。
开发深度学习模型
让我们从简短回顾一下什么是深度学习。深度学习的核心基础构建块是神经网络。神经网络是一种旨在模拟人类大脑的算法。它的构建块叫做神经元,模拟了人类大脑中数十亿个神经元。在神经网络的上下文中,神经元是存储简单信息(称为权重和偏置)的对象。把它们看作是算法的记忆。
深度学习架构本质上是具有三层或更多神经网络层的神经网络架构。神经网络层可以分为三大类——输入层、隐藏层和输出层。输入层是最简单的层组,其功能是将输入数据传递给后续层。该层组不包含偏置,可以看作是被动神经元,但它的连接中仍包含到后续层神经元的权重。隐藏层由包含偏置的神经元组成,并且与后续层的神经元之间有权重连接。最后,输出层由与类别数量和问题类型相关的神经元组成,并包含偏置。计算神经网络层时的最佳实践是排除输入层。因此,具有一个输入层、一个隐藏层和一个输出层的神经网络被视为一个两层神经网络。下图展示了一个基本的神经网络,也叫做多层感知器(MLP),它有一个输入层、一个隐藏层和一个输出层:

图 1.12 – 一个简单的深度学习架构,也叫做 MLP
作为更广泛机器学习类别的一个子集,深度学习模型能够通过损失函数和优化算法从数据中学习模式,优化算法用于优化损失函数。损失函数定义了模型的误差,以便更新其记忆(权重和偏差),使其在下一次迭代中表现得更好。优化算法是一种决定根据损失值更新权重的策略的算法。
通过这个简短的回顾,让我们深入总结一下常见的深度学习模型家族。
深度学习模型家族
这些层可以有多种形式,因为研究人员已经能够发明新的层定义来应对新的问题类型,并几乎总是配备有非线性激活函数,使得模型能够捕捉数据之间的非线性关系。随着层的变化,出现了许多不同的深度学习架构家族,专门针对不同的问题类型。一些最常见和广泛使用的深度学习模型如下:
-
多层感知机(MLP)用于表格数据类型。将在第二章中详细探讨,设计深度学习架构。
-
卷积神经网络用于图像数据类型。将在第三章中详细探讨,理解卷积神经网络。
-
自编码器用于异常检测、数据压缩、数据去噪和特征表示学习。将在第五章中详细探讨,理解自编码器。
-
门控循环单元(GRU)、长短期记忆网络(LSTM)和变压器用于序列数据类型。这些将在第四章中详细探讨,理解循环神经网络,以及第六章中详细探讨,理解神经网络变压器。
这些架构将是第 2 到第六章的重点,我们将讨论它们的方法论,并进行一些实际评估。接下来,让我们探索深度学习中可以解决的问题类型。
模型开发策略
今天,由于深度学习框架如 PyTorch 和 TensorFlow 的出现,以及它们的高级库封装,深度学习模型的发明和创建变得容易。此时,你应该选择哪个框架,取决于它们的接口偏好,因为这两个框架都经过多年改进,已经成熟。只有在迫切需要一个非常自定义的功能来应对独特问题时,才需要选择能够执行你所需要的框架。一旦选择了深度学习框架,深度模型的创建、训练和评估过程基本上就涵盖了所有内容。
然而,这些框架并不直接提供模型管理功能。模型管理是一个技术领域,它使团队、企业和深度学习从业者能够可靠、快速和高效地构建模型、评估模型、交付模型洞察、将模型部署到生产环境并管理模型。模型管理有时也被称为机器学习运维(MLOps)。你可能仍然在想,为什么需要这样的功能,尤其是如果你曾在 Kaggle 这样的平台上构建过深度学习模型,Kaggle 提供数据和机器学习问题作为竞赛。那么,以下是一些推动你需要利用这些功能的因素:
-
手动比较模型很繁琐:
- 手动在 Excel 表格中输入性能数据以追踪模型性能既慢又不可靠
-
模型产物很难追踪:
-
一个模型有许多产物,比如它的训练权重、性能图表、特征重要性和预测解释
-
比较模型产物也很繁琐
-
-
需要进行模型版本管理,以确保模型构建实验不会被重复:
-
用最可靠的模型洞察覆盖表现最好的模型,是你最不希望经历的事情
-
版本控制应该依赖于数据分区方法、模型设置和软件库版本
-
-
部署和管理模型并不简单
根据参与项目的团队规模以及组件需要重复使用的频率,适合使用不同的软件和库。这些软件和库分为付费和免费的(通常是开源的)类别。Metaflow,一个开源软件,适用于数据科学团队规模较大的情况,其中组件需要在其他项目中频繁复用,而MLFlow(开源软件)更适合小型团队或单人团队。其他值得注意的模型管理工具包括Comet(付费)、Weights & Biases(付费)、Neptune(付费)和Algorithmia(付费)。
这样,我们已经简要概述了深度学习模型开发的方法和策略;接下来我们将在章节中深入探讨模型开发的相关话题。但在此之前,让我们继续概览模型洞察的交付主题。
交付模型洞察
模型性能指标,在仅用于模型比较和选择的情况下,通常不是可靠地获取最佳模型的最有效方法。当人们关注机器学习模型可能做出的决策时,他们通常需要更多的信息和洞察力,以最终将信任寄托于模型的决策能力。最终,当模型不被信任时,它们就不会被部署。然而,信任不仅仅依赖于模型的洞察力。建立对模型的信任涉及确保准确、可靠和无偏的预测,这些预测与领域专业知识和商业目标一致,同时向利益相关者提供有关模型性能指标、决策逻辑及其预测背后理由的洞察。解决潜在偏见并展示公平性是赢得对模型可靠性信心的关键。这个持续的信任建立过程超越了初始部署,因为模型必须始终展现合理的决策过程、证明预测的合理性,并保持无偏的表现。通过建立信任,模型成为一个有价值且可靠的工具,能在现实应用中发挥作用,推动各个领域和行业的广泛采用和利用。
提供对业务有价值的模型洞察。除了为确保模型信任并消除信任问题而提供模型洞察外,实际的性能指标同样重要。尽可能将模型指标转化为通俗易懂的商业指标,以有效地传达模型可能对业务带来的积极影响。成功指标应在规划阶段定义,并在此阶段报告实际值。
在模型部署后,建立对模型的信任并不会停止。类似于人类在生活中需要解释自己的决策,机器学习模型(如果预期替代人类来自动化决策过程)也需要如此。这一过程被称为预测解释。在某些情况下,模型的决策被期望作为参考,其中会有一个人类专家参与验证决策,确保决策正确后再执行。预测解释在这种情况下几乎总是必需的,因为模型的使用者希望了解模型为何做出预测,而不是直接使用预测结果。
模型洞察力也可以帮助你提升模型的表现。记住,机器学习生命周期本质上是一个迭代过程。以下是一些可能出现这种情况的具体示例:
-
你意识到模型对某一特定群体存在偏见,并决定回到数据获取阶段,增加来自该群体的数据,或者更换为对偏见具有鲁棒性的建模技术。
-
你意识到模型在某一类上的表现不好,然后回到模型开发阶段,使用一个不同的深度学习模型损失函数,专注于表现不佳的那一类。
深度学习模型被认为是一个黑箱模型。然而,实际上,今天已有许多关于深度学习解释方法的研究论文,这些方法使得深度学习突破了黑箱的局限性。我们将在本书的第二部分深入探讨我们可以如何解释并为深度学习模型提供洞见。
现在我们对深度学习生命周期中的相关过程有了更多的了解,在接下来的部分,我们将讨论你需要关注的、可能贯穿整个项目生命周期的风险。
风险管理
深度学习系统从一开始就暴露于多种风险中,从构思到系统采纳。通常,分配到深度学习项目中的大多数人仅负责机器学习生命周期的某个特定阶段,如模型开发或数据准备。当生命周期的一个阶段的工作在后期产生问题时,尤其是当相关团队成员缺乏对整个大局的理解时,这种做法可能会带来不利影响。深度学习系统中涉及的风险通常与机器学习生命周期各个阶段之间的交互有关,并遵循垃圾进,垃圾出的概念。确保所有参与构建系统的人都对系统最终输出的结果负责,而不仅仅是各个独立阶段的结果,这是管理机器学习系统风险的基础要素之一。
那么,风险是什么呢?让我们从一个可以在任何实际成果产生之前就能处理的问题开始——也就是在评估用例的价值时发生的事情。
伦理和监管风险
深度学习几乎可以应用于任何行业,但一些最难以在其中推广深度学习的行业是高度监管的行业,如医疗和金融行业。这些行业的法规最终决定了深度学习系统在行业中能做什么或不能做什么。这些法规大多由政府引入,通常涉及伦理和法律的考量。在这些高度监管的行业中,常常会出现每月、每周甚至每天进行的审计,以确保公司符合所施加的法规。某些行业之所以受到更加严格的监管,主要是因为这些行业的任何行为所带来的后果会对人民或国家的福祉造成巨大的成本。深度学习系统需要以符合这些法规的方式进行构建,以避免面临停用的风险,无法被采纳的风险,甚至最严重的,面临监管官员巨额罚款的风险。
有时,深度学习模型的表现可以远超其人类同行,但另一方面,没有任何深度学习模型是完美的。人类会犯错,大家都知道这一点,但我们需要意识到的另一个现实是,机器无疑也会犯错。最大的风险是,当人类过度信任机器,甚至完全依赖机器做决策时!那么,我们该如何考虑这些错误,并且谁将为这些错误负责呢?
让我们看看在人类没有机器的情况下,我们会怎么做。当风险较高时,重要的决策总是需要经过一系列的审批层级,才能最终做出决定。这些审批层级表明了重要决策需要有责任和可靠性。在决策过程中获得的审批越多,我们就越能说自己在做出这一重要决策时充满信心。一些常见的需要审批层级的重要决策包括:在公司中决定雇佣一名员工、决定向客户收取的保险费用,或者决定是否将资金投入到某个企业。考虑到这一点,深度学习系统在面对高风险应用时,需要具备类似的审批工作流程,以便在深度学习模型做出某个预测决策时获得批准。这些工作流程可以包括任何形式的洞察力,解释预测结果可以帮助领域专家更容易地判断决策的有效性。增加人类的参与使得深度学习系统变得更加符合伦理,并且足够值得信赖,能够成为高风险决策流程的一部分。
让我们以一个医疗行业的使用案例为例——例如,通过 X 光扫描预测肺病的使用案例。从伦理角度来看,深度学习模型完全有能力通过预测极其危害健康的疾病,如晚期肺癌,剥夺一个人的生存希望,这显然是错误的。如果预测到的极端疾病被误诊,患者可能会不必要地感到悲痛,或花费不必要的金钱进行昂贵的检查来验证结果。在深度学习系统中设立审批工作流程,允许医生将这些结果作为辅助方法使用,将解决使用自动决策系统时可能产生的伦理问题。
业务背景不匹配
重申在定义成功部分提到的观点,将所需的深度学习输入数据和目标标签选择与业务背景对齐,并了解这些目标预测如何被使用,是深度学习系统能否被采纳的关键所在。这里的风险在于,当业务价值未被恰当地定义,或者深度学习系统未能正确匹配时,可能会导致系统的失败。即使目标适合业务背景,如何消费这些预测才是获取价值的关键。如果在任何方面未能与业务背景匹配,系统可能会被拒绝。
理解业务背景涉及了解目标用户群体是谁,以及他们不包括谁。这个过程中的一个关键步骤是记录和构建用户群体画像。用户画像包含了关于他们的工作流程、历史、统计数据和关注点的信息,这些信息应当提供构建一个符合潜在用户具体需求的系统所需的背景。不要害怕进行市场调研,并在构建用户画像的过程中对目标用户需求进行适当验证。毕竟,构建一个深度学习系统需要付出大量努力,如果浪费时间构建一个没人愿意使用的系统,那就得不偿失。
数据收集和标注的风险
你的机器学习模型的效果将取决于数据的质量。与机器学习相比,深度学习需要大量更多的数据。此外,通常对于新的深度学习使用案例,数据不是现成的,需要手动收集并精心标注。确保数据收集和标注的方式能够保证质量,是一项非常艰巨的任务。
用于收集和标注数据以供深度学习使用的策略和方法因使用案例不同而有所差异。有时,数据收集和标注过程可以同时进行。这种情况可以发生在自然标签存在时,或者在数据收集之前已经预先指定标签时:
-
自然标签 是在一段时间后通常会自然产生的标签。例如,这可以是通过超声波图像确定婴儿的性别;另一个用例是房屋价格作为标签,其输入数据为该房产的一系列图像。
-
预先指定的标签 是在收集输入数据时预先确定的标签。例如,这可以是在受控环境中收集的一些语音数据的性别标签,仅用于构建机器学习模型,或者在拍摄面部照片以构建机器学习模型之前的某项调查中的个人年龄。
这两种类型的标签相对来说风险较低,因为标签很可能是准确的。
一种最终的数据收集和数据注释方法,具有相当大的风险,是在收集数据后进行标注。后注释 需要对所需标签的特征具有某种形式的领域知识,并且由于人为评估错误,不总是导致 100%的真实值。无意识的标注错误发生在标注者仅仅因为偶然而导致错误。另一方面,有意识的标注错误发生在标注者有意决定以错误的标签决策。这种意图可能源于数据中某种模式所根深蒂固的或宽泛的信念,或者仅仅是为某种原因故意制造的明显错误。这里所呈现的风险是标签可能被错误地注释。幸运的是,有几种策略可以执行以消除这些风险;这些将在以下段落中介绍。
由于人类易于出现一定程度的错误,无意识的标注错误很难避免。然而,不同的人通常有不同程度的注意力和选择性标签者的选择可能对某些人来说是一种可行的选择。如果标签者仅因金融补偿而被聘请来标注您的数据标签,另一个可行的选择是在提供需要标记数据的数据之间周期性地提供已经标记的秘密数据以供标签者标记。这种策略允许您评估单个标签者的表现,并根据其注释工作的准确性提供补偿(是的,我们也可以使用用于评估机器学习模型的指标)。作为积极的副作用,标签者还将被激励在他们的注释工作中表现更好。
有意识的标注错误是这里最危险的风险,因为它们可能影响整个数据集的质量。当对与任何目标标签相关的数据模式达成错误共识,并应用于整个标注过程中时,标签的合法性直到机器学习生命周期的后期才会被发现。机器学习模型仅具备学习所需模式的技术,以便将输入数据映射到提供的目标标签,即使标签是错误的,它们也很可能表现得很好。只要标注者在标注过程中强加了某种模式,不论对错,机器学习模型都会尽力学习所需的模式,以执行准确的输入到输出映射。那么,我们如何减轻这种风险呢?确保在领域专家的帮助下正确定义意识形态,在减轻过程中起着重要作用,但这一策略本身的有效性因涉及的领域专家数量、专家的专业水平、标注者数量以及收集数据的异常程度或质量而有所不同。
即使是领域专家有时也会犯错。以医生为例——你有多少次听说过医生开错药方或做出错误的医学评估?你有多少次误听别人说话后不得不猜测对方说了什么?在最后一个例子中,领域专家就是你,而领域是语言听力理解。此外,当标注者超过一个人并形成标注团队时,一个最突出的风险就是不同偏好的领域意识形态之间的标注数据不匹配。有时,这种情况发生是因为某些标签在数字格式中可能存在不同的变体,或者存在让标注者分析能力受阻的混乱模式。有时,这也可能是由于标注者或领域专家对某个标签或输入数据的固有偏见。随后,数据中的偏见会导致模型中的偏见,并引发伦理问题,从而削弱对机器学习模型决策的信任。当对机器学习模型缺乏信任时,项目将无法被采纳,并失去其商业价值。关于偏见、公平和信任的主题将在第十三章中更广泛地讨论,该章将详细阐述偏见的来源及其缓解方式。
标签错误但仍有正确标签的数据称为噪声标签。幸运的是,深度学习中有一些方法可以帮助你绕过噪声标签,例如弱监督学习。然而,请记住,最好的做法是从源头上解决问题,即在数据收集和标注时解决,而不是事后再修正。让我们深入探讨另一种可以使标注过程更安全的策略。深度学习项目的数据标注过程通常涉及使用软件工具来标注特定所需的标签。软件工具使得标注工作更快、更轻松,使用标注协作软件工具可以让标注工作更加防错。一个好的协作标注工具会让标注人员以某种方式对齐他们的数据发现,从而促进标注时的一致性。例如,当识别到容易误解的模式时,自动通知整个标注团队,可以帮助防止更多数据被错误标注,并确保之前相关的数据得到重新审查。
最后需要提到的是,未标记数据并不是一个风险,而是机器学习中巨大的潜力。尽管它缺乏具体的标签来实现特定目标,但数据之间存在固有的关系是可以学习的。在第九章《探索无监督深度学习》中,我们将探讨如何利用无监督学习将这些数据作为后续监督学习的基础,这就是更广为人知的半监督学习工作流程。
接下来,我们将讨论与机器学习模型使用数据时相关的另一个安全风险。
数据安全风险
在机器学习的背景下,安全性与防止数据被未经授权使用、保护数据隐私以及防止与数据使用相关的非预期事件或攻击有关。这里的风险是当数据安全受到破坏时,可能会导致未能遵守监管标准、模型性能的下降,或商业流程的破坏与损毁。在本节中,我们将介绍四种数据安全风险,分别是敏感数据处理、数据许可、软件许可和对抗性攻击。
敏感数据处理
一些数据比其他数据更敏感,并且可能与监管风险相关。敏感数据引发了数据隐私法规,这些法规管理在不同法域中个人数据的使用。法规的具体内容有所不同,但通常围绕合法保护个人数据使用权利,并要求对这些数据采取的任何行动都必须获得同意,同时在使用此类数据时需要遵循相关条款。这些法规的例子包括涵盖欧盟的通用数据保护条例(GDPR)、涵盖新加坡的个人数据保护法(PDPA)、仅涵盖美国加利福尼亚州的加利福尼亚消费者隐私法(CCPA)以及涵盖美国弗吉尼亚州的消费者数据保护法(CDPA)。这意味着,你不能仅仅收集被归类为个人的数据显示、标注数据、建立模型并部署,而不遵守这些法规,因为在某些法域中,这样做会被视为犯罪。除了请求同意外,常用的一种方法是通过匿名化数据来减轻这种风险,使得数据无法被任何单个个体识别。然而,匿名化必须可靠地进行,以确保关键的通用信息得以保留,同时可靠地消除任何重建个人身份的可能性。确保敏感和个人数据得到妥善处理,在建立机器学习模型的决策信任方面有着重要作用。在处理敏感和个人数据时,务必保持极高的谨慎,以确保你的机器学习项目的长期可持续性。
数据与软件许可
尤其对于深度学习项目来说,构建一个高质量的模型需要大量数据。公开可用的数据集的存在有助于通过部分消除收集和标注数据所需的成本和时间,从而缩短项目的时间和复杂性。然而,大多数数据集与软件一样,都有相关的许可协议。这些许可协议规定了与其关联的数据可以如何使用。关于商业问题的机器学习模型,数据许可的最重要标准是数据是否允许商业使用。由于大多数商业用例因其产生的利润而被视为商业用例,因此具有禁止商业使用条款的数据集不能使用。防止商业使用的许可条款的一个例子是所有衍生自创作共用署名-非商业性使用(CC-BY-NC)许可的条款。同样,开源软件也会对深度学习项目构成风险。在使用任何公开可用的数据或软件之前,一定要确保三次检查其许可协议。将具有防止商业使用条款的数据或代码用于商业项目,会使项目面临因许可侵权而被罚款或起诉的风险。
对抗攻击
当机器学习应用广泛时,它会暴露出自身容易受到针对性攻击的风险,这些攻击旨在恶意地破坏和操控模型的决策。这引出了一个可能影响已部署深度学习模型的攻击类型,称为对抗性攻击。对抗性攻击是一种攻击方式,通过特定的方式操控数据输入,从而影响机器学习模型的预测。最常见的对抗性攻击是通过对实际输入数据进行修改,生成对抗性数据示例。这些修改后的数据看起来仍然符合输入数据的合法性,但却能扭曲模型的预测。此类攻击的风险程度在不同的使用场景中有所不同,具体取决于用户与系统的交互程度,最终将输入数据传递给机器学习模型。对于深度学习模型来说,最广为人知的对抗性示例之一是经过优化的图像,它看起来像是随机的色彩噪声。当这种图像用于扰动另一张图像的像素值,通过覆盖其像素值时,就会生成一个对抗性示例。扰动后的原始图像从视觉上看起来与人眼几乎无差,但却能导致错误的误分类。下图展示了这种扰动的示例,摘自第十四章,分析对抗性表现。该图展示了一个神经网络 ResNet50 的结果,ResNet50 经过训练可以分类人脸身份,当给定人脸图像时,它能够正确预测身份。然而,当该图像与另一个噪声图像数组结合在一起时,这个噪声图像数组是通过访问预测类别概率自动生成的,模型错误地预测了人脸图像的身份,即使合成后的图像与原始图像在视觉上完全相同:

图 1.13 – 潜在的基于图像的对抗性攻击示例
理论上,对抗性攻击可以使用任何类型的数据进行,并不限于图像。随着机器学习应用场景的风险增大,攻击者更有可能投入资金组建研究团队,以产生新的对抗性示例。
一些对抗样本及其生成方法依赖于对深度学习模型本身的访问,以便制作定向对抗样本。这意味着,潜在的攻击者除非能够访问该模型,否则几乎不可能成功地混淆别人创建的模型。然而,许多企业直接使用公开可用的预训练模型,并应用迁移学习方法,以减少满足业务用例所需的工作量。任何公开可用的预训练模型也同样对攻击者开放,允许他们为个别模型构建定向对抗样本。这类预训练模型的例子包括所有公开可用的 ImageNet 预训练卷积神经网络模型及其权重。
那么,我们如何尝试减轻这种风险呢?
我们可以用来减轻对抗性攻击风险的方法之一,是通过使用公共研究中的已知方法集的示例来训练深度学习模型。通过使用已知的对抗样本进行训练,模型将学会在学习过程中忽略这些对抗信息,并在验证和推理阶段不受此类样本的影响。评估不同变体的对抗样本还帮助我们设定期望,了解模型何时会失败。
在这一节中,我们深入探讨了处理和使用数据进行机器学习时的安全问题。在第十四章,分析对抗性表现中,我们将深入实践评估针对不同数据模态的对抗性攻击技术,并探讨如何在深度学习的背景下减轻这些攻击。接下来,我们将深入探讨模型开发过程中的另一个核心风险类别。
过拟合和欠拟合
在模型开发过程中,即训练、验证和测试机器学习模型的过程中,最基础的风险之一就是过拟合和欠拟合。
过拟合是指机器学习模型对提供的训练数据集样本和已学习的模式产生过度偏向,以至于它只能区分训练数据集中的样本,而无法区分验证集和测试集中的任何样本。
欠拟合则是指机器学习模型未能捕捉到提供的训练、验证和测试数据集中的任何模式。
学习可泛化的模式输出映射能力是构建有价值且可用的机器学习模型的关键。然而,达成一个良好拟合的模型并没有银弹,通常需要在数据准备、模型开发和模型交付洞察阶段之间反复迭代。
以下是一些防止过拟合并确保深度学习中泛化的建议:
-
预测最终部署条件时,增加数据增强
-
收集足够的数据,覆盖目标标签的每一种可能变异
-
与领域专家合作,了解关键指标和模式
-
使用交叉验证方法,确保模型在未见数据上进行公平评估
-
尽可能使用更简单的神经网络架构
以下是一些防止欠拟合的建议:
-
评估各种不同的模型
-
与领域专家合作,了解关键指标和模式
-
确保数据清洁,尽可能减少错误
-
确保数据足够充足
-
在构建模型时,从少量数据输入开始,逐步增加更复杂的数据,以确保模型能在数据上恰当地拟合
在本节中,我们讨论了构建模型时的问题。接下来,我们将讨论一种在模型训练完成后会影响模型的风险。
模型一致性风险
机器学习过程的一个主要特征是它是循环的。模型会不断重新训练,旨在寻找更好的设置。这些设置可能是不同的数据划分方法、不同的数据转换方法、不同的模型,或是相同的模型但具有不同的模型设置。通常,构建的模型需要相互公平且公正地进行比较。模型一致性是确保不同模型版本和性能指标之间能够进行公平比较的唯一特征。在获得模型之前的每个过程都要求保持一致性,以便当任何人尝试用相同的设置执行相同的过程时,应该能够获得和再现相同的模型。我们需要重申,即使在构建模型时某些过程需要随机性,它也需要是可随机确定的。这是必要的,以确保设置中的唯一差异是目标设置,而不是其他任何因素。
模型一致性不仅仅体现在模型的可重现性上——它还扩展到模型预测的一致性。当使用不同的批量大小设置进行预测时,预测结果应该是相同的,而且相同的数据输入应始终产生相同的预测结果。模型预测中的不一致性是一个重大的警示信号,表明模型所产生的任何结果都无法代表在部署时的实际表现,任何由模型得出的见解都可能是误导信息。
为了应对模型一致性风险,始终确保通过尽可能地种子化随机数生成器,使代码产生一致的结果。在模型开发阶段,构建模型后,始终手动或自动验证模型的一致性。
模型退化风险
当你构建了一个模型、验证了它,并展示了它对业务的影响后,你接着将其进入模型部署阶段并准备好投入使用。一个常见的错误是认为这就意味着深度学习项目的结束,你可以把手放开,任由模型自我运行。遗憾的是,大多数机器学习模型会退化,这取决于你在模型开发阶段实现的泛化程度,以及在实际环境中可用数据的情况。一个常见的场景是,当模型被部署时,最初模型的表现和部署过程中接收到的数据特征保持一致,并随着时间推移发生变化。时间有可能改变环境条件以及世界上的任何事物。机器老化,季节变化,人也会改变,预见到这些条件和变量的变化可以帮助你确保模型在业务中保持相关性和影响力。
模型退化的方式可以分为三类,分别是数据漂移、概念漂移和模型漂移。从概念上讲,漂移可以与一艘船缓慢漂离其理想位置相联系。在机器学习项目中,不是船漂离,而是数据或模型从其原始的感知行为或模式中漂离。我们简要地介绍一下这几种漂移类型。
数据漂移是一种退化现象,涉及到模型所需提供预测值的输入数据。当一个部署后的模型经历数据漂移时,意味着在部署过程中接收到的数据不属于机器学习模型在训练和验证过程中所用数据的固有分布。如果有方法验证在部署过程中提供的新数据上的模型,数据漂移可能会导致模型验证期间原始预期指标性能的变化。一个深度学习模型中数据漂移的例子可以是在户外预测人类行为的用例。在这个用例中,数据漂移表现为原始数据是在夏季收集的,数据中包括穿夏季服装的人,而由于冬季的到来,人们则穿上了冬季服装。
概念漂移是一种与输入数据如何与目标输出数据交互变化相关的退化形式。在规划阶段,领域专家和机器学习从业者合作定义可能影响目标输出数据的输入变量。这个定义好的输入和输出设置随后将用于构建机器学习模型。然而,有时并不是所有可能影响目标输出数据的上下文都会作为输入变量包含在内,这可能是由于数据的可获得性或缺乏领域知识的原因。这就引入了依赖于缺失上下文条件的情况,这些缺失的上下文与为机器学习收集的数据相关。当缺失的上下文条件与训练和验证数据中存在的基本值发生漂移时,就会发生概念漂移,导致原始概念变得无关或发生偏移。简而言之,这意味着相同的输入不再映射到训练数据中的相同输出。在深度学习的背景下,我们可以以一个基于文本数据的情感分类用例为例。假设一个评论或讲话根据司法管辖区和文本本身可以是负面、中立或正面情感。这意味着在某些司法管辖区,人们对什么被视为负面、中立或正面的阈值不同,并且对事物的评分方式也不同。仅使用文本数据训练模型将无法准确地跨司法管辖区进行泛化,因此每当模型在另一个司法管辖区部署时,它都会面临概念漂移。
最后,模型漂移是一种与操作指标和易于测量的指标相关的退化形式。可以归类为模型漂移的退化因素包括模型延迟、模型吞吐量和模型错误率。与其他两种漂移类型相比,模型漂移的指标通常更容易衡量和跟踪。
缓解这些风险的最佳工作流程之一是跟踪这三种类型漂移下的指标,并为新机器学习模型的构建提供清晰路径,这个过程我称之为漂移重置。现在我们已经简要概述了模型退化,在第十六章《深度学习模型的管理》和第十七章《在动态环境中有效管理漂移效应》中,我们将更深入地讨论这些风险,并实际探讨如何在深度学习的背景下缓解这一风险。
总结
在本章中,你学到了如何以一种可重复、一致的方式成功完成深度学习项目,虽然这是从中高层次的视角来看。我们在本章讨论的主题被结构化为深度学习生命周期早期阶段的更全面内容,这包括规划和数据准备阶段。
在接下来的章节中,我们将更全面地探讨生命周期的中后期阶段。这涉及数据准备阶段之后的所有内容,包括模型开发、模型洞察、模型部署以及最后的模型治理。
在下一章,我们将更深入地探索常见和广泛使用的深度学习架构。
进一步阅读
-
石博文、徐维宁、阿卜杜勒拉赫曼·穆罕默德。强大的自监督视听语音识别,2022 年。芝加哥丰田技术学院,Meta AI:
arxiv.org/pdf/2201.01763v2.pdf。 -
王鹏、杨安、门锐、林俊阳、白帅、李志康、马建新、周畅、周靖仁、杨红霞。通过简单的序列到序列学习框架统一架构、任务和模态。达摩院,阿里巴巴集团:
arxiv.org/pdf/2202.03052v1.pdf。
第二章:设计深度学习架构
在上一章中,我们回顾了整个深度学习生命周期,并理解了从头到尾使深度学习项目成功的含义。有了这些知识后,我们现在准备进一步深入探讨深度学习模型的技术细节。本章将深入探讨业界常用的深度学习架构,并理解每种架构设计背后的原因。对于中级和高级读者,这将是一个简要回顾,以确保术语的定义一致性。对于初学者,本章将以易于理解的方式呈现架构,帮助你快速掌握深度学习领域中有用的神经网络架构。
理解多种架构背后的方法论可以让你创新适用于特定使用案例的自定义架构,最重要的是,能够根据数据输入或问题类型选择合适的基础架构。
本章将重点介绍多层感知机(MLP)网络架构。全面讲解 MLP 及一些与神经网络实现相关的关键概念,如梯度、激活函数和正则化方法,为后续章节中更复杂架构类型的探索奠定基础。具体来说,本章将涵盖以下内容:
-
使用 MLP 探索神经网络的基础
-
理解神经网络的梯度
-
理解梯度下降
-
从零开始实现 MLP
-
使用深度学习框架实现 MLP
-
设计 MLP
技术要求
本章包括一些使用Python编程语言的实践实现。为完成这些内容,你需要在计算机上安装以下库:
-
pandas -
Matplotlib -
Seaborn -
Scikit-learn -
NumPy -
Keras -
PyTorch
代码文件可以在 GitHub 上找到:github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_2。
使用 MLP 探索神经网络的基础
深度学习架构是通过至少使用三个感知机层(不包括输入层)来创建的。感知机是由神经元单元组成的单层网络。神经元单元包含一个偏置变量,并作为与其他神经元连接的节点。这些神经元将与不同层中的其他神经元进行交互,连接/节点之间应用权重。感知机也称为全连接层或密集层,而 MLP 也被称为前馈神经网络或全连接****神经网络。
让我们回顾上一章的 MLP 图示,以便更好地理解。

图 2.1 – 简单的深度学习架构,也叫做 MLP
该图显示了如何将三个数据列输入传递到输入层,然后逐步传播到隐藏层,最后通过输出层。尽管图中没有展示,但在隐藏层和输出层的输出上应用了额外的激活函数。隐藏层的激活函数为模型添加了非线性,使神经网络能够捕捉输入数据和输出数据之间的非线性关系。输出层使用的激活函数取决于问题类型,具体内容将在第八章《探索监督式深度学习》中更详细地讨论。
在深入讨论相关的隐藏激活方法之前,我们首先需要了解梯度消失问题。梯度消失问题是指在反向传播过程中,损失函数相对于模型参数的梯度变得非常小。这可能导致学习速度缓慢和收敛效果差,因为权重更新极少甚至没有更新。梯度消失问题在使用将输入值压缩到狭窄范围的激活函数时尤为突出。为了解决这个问题,Rectified Linear Unit (ReLU) 激活函数得到了广泛应用,因为它能够在一定程度上缓解梯度消失问题。ReLU 将负值映射为零,保持正值,如图 2.2 所示。

图 2.2 – ReLU、Leaky ReLU 和 PReLU 输入/输出图
除了 ReLU,还有其他一些有用的隐藏层激活函数,它们有助于缓解梯度消失问题,同时提供各种优点。以下是其中一些:
-
Leaky ReLU:Leaky ReLU 是 ReLU 函数的一种变体,它允许负输入值有一个小的、非零的梯度。这有助于缓解“死亡 ReLU”问题,当神经元的输入值持续为负时,神经元会变得不活跃并停止学习。Leaky ReLU 为负输入值引入了一个小的斜率,确保梯度不会完全消失。
-
Parametric ReLU (PReLU):PReLU 是 ReLU 函数的另一种变体,其中负斜率在训练过程中进行学习,使得模型能够自适应其行为。这种灵活性可能会提高性能,但代价是增加了复杂性并存在过拟合的风险。
此外,随着我们在本书中深入探讨不同的主流架构,我们将探索更多的隐藏激活函数。这些激活函数各有优缺点,激活函数的选择取决于要解决的具体问题和所使用的架构。理解、实验和评估这些激活函数对于在神经网络的隐藏层中选择最适合的激活函数至关重要。此外,评估与模型构建相关的任何实验的推荐方法将在第八章中进行探讨,探索监督式深度学习。
接下来,从一层传播到另一层的过程称为前向传递或前向传播,其公式一般可以定义如下:
a = g( ∑ n=0 神经元 wx + b)
在这里,a 表示神经网络层的输出(称为激活),g 表示非线性激活函数,w 表示神经元连接之间的权重,x 表示输入数据或激活值,b 表示神经元的偏置。不同类型的神经网络层以不同方式消耗和输出数据,但通常仍以此公式为基础。
理解神经网络梯度
MLP(多层感知机)中的机器学习目标是找到能够有效地将输入映射到期望输出的权重和偏置。权重和偏置通常是随机初始化的。在训练过程中,使用提供的数据集,它们会以批次的方式进行迭代更新,以最小化损失函数,该函数使用一种叫做反向传播(backpropagation)的方法计算梯度。批次是用于训练或评估的数据集的一个子集,允许神经网络以较小的组处理数据,而不是一次性处理整个数据集。损失函数也被称为误差函数或代价函数。
反向传播是一种技术,用来找出每个神经元的权重和偏置变化对总体损失的敏感度,方法是利用损失函数相对于权重和偏置的偏导数。来自微积分的偏导数是衡量函数相对于某一变量变化速率的工具,使用一种叫做微分的技术,在神经网络中得到有效应用。一种方便的方法叫做链式法则,它允许通过分别计算每个函数的偏导数,即神经网络中的前向传播,来得到神经网络的导数。明确来说,导数可以称为变化的敏感度、梯度或变化率。其基本思想是,当我们知道哪个模型参数对误差的影响最大时,可以根据其幅度和方向相应地更新其权重。让我们以一个简单的两层多层感知机(MLP)为例,每一层有一个神经元,如图 2.3所示,来理解这一过程。

图 2.3 – 两层多层感知机的示意图
为了更清晰,w表示神经元连接的权重,b表示神经元的偏置,L表示层级。不同问题类型需要不同的损失函数,但为了说明问题,我们假设这是一个回归问题的 MLP,在这里我们使用均方误差作为损失函数,从最终层激活值和数值目标值中计算损失组件。损失函数可以定义为以下形式:
L = 1 _ n ∑ i=1 n (a2 − y) 2
这里,n 表示神经元的总数。为了获得损失函数相对于输出层权重w2的变化率δL _ δw2,让我们基于链式法则定义公式。考虑以下内容:
z2 = w2 ∙ a1 + b2
所以,如果 a2 = g(z2),其中 g 是 ReLU 函数,那么输出层权重 w2 的梯度将定义如下:
δL _ δw2 = δL _ δa2 ∙ δa2 _ δz2 ∙ δz2 _ dw2
损失函数相对于 w2 的变化率可以通过将三个独立的变化组件相乘来计算:即损失函数相对于第二层输出的变化,激活输出相对于没有激活的包装 z2 的变化,以及包装 z2 相对于 w2 的变化。接下来我们定义这些组件。现在考虑以下内容:
e = a2 − y
基于链式法则,第一个变化组件将被定义为以下内容:
δL _ δa2 = δL _ δe ⋅ δe _ δa2
δL _ δe = 2e
δe _ δa2 = 1
将这个简化的组件表示放入公式中,得到以下方程:
δL _ δa2 = 2 _ n (a2 − y)
对于第二个变化组件,它可以通过以下公式定义:
δa2 _ δz2 = g ′ (z2) = 1
在此情况下,输出层没有应用激活函数。
对于第三个也是最后一个变更组件,它可以用以下公式来定义:
δz2 _ δw2 = a1
最后,将三个组件的简化表示代入公式中,以获得输出层权重 w2 的梯度,结果为以下方程:
δL _ δw2 = 2 _ n(a2 − y) ∙ a1
现在你需要做的就是将实际值代入公式,以获得第二层的权重梯度。相同的公式结构可以类似地应用于隐藏层的权重 w1,如下所示:
a1 = g(z1)
z1 = w1 ∙ a0 + b1
δL _ δw1 = δL _ δa1 ∙ δa1 _ δz1 ∙ δz1 _ δw1
在使用链式法则展开 δL _ δa1 后,公式可以变为以下形式:
δL _ δw1 = δL _ δa2 ∙ δa2 _ δz2 ∙ δz2 _ δa1 ∙ δa1 _ δz1 ∙ δz1 _ δw1
额外的各个组件可以定义如下:
δz2 _ δa1 = w2
δa1 _ δz1 = g ′ (z1) = 0 如果 a2 < 0,1 如果 a2 > 0
δz1 _ δw1 = a0
这里的 a0 是输入数据。现在,我们定义隐藏层权重 w1 的梯度,并用表示法来代入实际值进行计算,如下所示:
δL _ δw1 = 2 _ n (a2 − y) ∙ w2 ∙ g ′ (z1) ∙ a0
相同的过程可以重复应用于偏置项,以获得其梯度。只需将 z 关于权重的偏导数替换为 z 关于偏置的偏导数,如下所示:
δz2 _ δb2 = δz1 _ δb1 = 1
δL _ δb2 = δL _ δa2 ∙ δa2 _ δz2 ∙ δz2 _ δb2
δL _ δb2 = 2(a2 − y)
δL _ δb1 = δL _ δa2 ∙ δa2 _ δz2 ∙ δz2 _ δa1 ∙ δa1 _ δz1 ∙ δz1 _ δb1
现在,我们定义第一个偏置项的梯度,并用表示法来代入实际值进行计算,如下所示:
δL _ δb1 = 2 _ n ∙ w2 ∙ (a2 − y) ∙ g ′ (z1)
之前定义的公式是特定于示例神经网络的,其中每层只有一个神经元。在实际应用中,这些层通常包含多个神经元。要计算包含多个神经元和多个数据样本的层的损失和导数,你只需对所有值进行平均。
一旦获得了梯度或导数,就可以使用不同的策略来更新权重。用于优化神经网络权重和偏置的算法称为优化器。如今有许多优化器选项,每种都有自己的优缺点。由于梯度用于优化权重和偏置,因此这个优化过程被称为梯度下降。
理解梯度下降
思考深度学习模型的损失,一种好的方式是将其视为一个三维损失景观,景观中有许多不同的山丘和谷底,谷底代表着更优的结果,如图 2.4所示。

图 2.4 – 一个示例损失景观
然而,实际上我们只能近似这些损失景观,因为神经网络的参数值可能存在无限多种组合。实践者通常用来监控每个训练和验证周期中损失表现的方式是简单地绘制一个二维折线图,其中 x 轴表示执行的周期数,y 轴表示损失表现。一个周期是神经网络训练过程中对整个数据集的单次迭代。图 2.4 中的损失景观是神经网络三维损失景观的近似。为了可视化 图 2.4 中的三维损失景观,我们可以使用两个随机初始化的参数和一个完全训练好的参数,这些参数来自神经网络中的相同神经元位置。损失可以通过对这三个参数进行加权求和来计算。完全训练好的参数的权重保持不变,而两个随机初始化的参数的权重则会被调整。这个过程使我们能够近似 图 2.4 中所示的三维损失景观。在这个图中,x 轴和 y 轴分别是来自同一个神经网络的两个随机初始化参数的权重,而 z 轴则是损失值。梯度下降的目标是尝试找到 全局 最深的谷底,而不是被困在 局部 谷底或 局部 最小值中。计算出来的梯度提供了建议的方向,用于逐步调整和更新权重与偏置。需要注意的是,梯度提供了最快增大损失函数的方向,因此在梯度下降时,参数会从梯度中被减去。让我们通过一个简单的梯度下降形式来了解如何更新权重和偏置:
w = w − α ∙ δL _ δw
b = b − α ∙ δL _ δb
在这里,α指的是学习率,它控制着你希望深度学习模型的训练进度。学习率是一个超参数,决定了神经网络在优化过程中学习和更新权重与偏置的速度。学习率越高,深度学习模型在损失函数中的步伐就越大。通过反复应用这个参数更新步骤,神经网络会慢慢地朝着下降的方向移动,从而使学习到的参数集能够有效地将输入映射到期望的目标值。所有数据样本的梯度会被计算并求平均,从而获得一个更新方向,用于更新权重和偏置。
数据集有时可能过大,导致基础梯度下降算法的学习过程变慢,因为在更新神经网络参数之前,需要从每个样本计算梯度。随机梯度下降法(SGD)就是为了解决这个问题而创建的。其核心思想是通过批次学习数据集,并通过不同的数据批次划分来迭代学习整个数据集,而不是等待从整个数据集获取梯度再更新网络参数。这样,即使数据集非常大,学习过程也能保持高效,并且能够快速看到初步结果。
梯度下降算法有很多变种,每种算法都有不同的优点,适用于特定的情况。在这里,我们将列出一些在各种数据集上表现良好且相关的梯度下降算法:
-
动量法(Momentum):动量法是 SGD 的一种变体,它引入了一个“动量”项,帮助优化器更有效地在损失函数的空间中导航。这个动量项是梯度的移动平均值,帮助优化器克服局部最小值并加速收敛。动量项还为优化器增加了一些惯性,使其在具有一致梯度的方向上采取更大的步伐,从而加速收敛。
-
均方根传播法(RMSProp):RMSProp 是一种自适应学习率优化算法,它为每个参数单独调整学习率。通过将学习率除以平方梯度的指数衰减平均值,RMSProp 有助于防止在 SGD 收敛过程中观察到的震荡现象。这使得收敛过程更加稳定且更快地趋向最优解。
-
自适应矩估计(Adam):Adam 是另一种流行的优化算法,它结合了 Momentum 和 RMSProp 的优点。它为每个参数维持独立的自适应学习率,并且还包含一个动量项。这种组合使得 Adam 能够快速收敛并找到更精确的解,因此它成为许多深度学习任务中的常见选择。
尽管有许多梯度下降算法可供选择,但选择合适的算法取决于具体问题和数据集。一般来说,由于 Adam 算法的自适应特性及其结合了 Momentum 和 RMSProp 的优点,它通常被推荐作为一个好的起点。为了确定最适合您特定深度学习任务的算法,必须尝试不同的算法及其超参数,并验证它们的性能。接下来,我们将使用 Python 编写一个多层感知机(MLP)模型。
从零开始实现多层感知机(MLP)
如今,创建神经网络及其层并进行反向传播的过程已经被封装在深度学习框架中。微分过程已经被自动化,不再需要手动定义导数公式。移除深度学习库提供的抽象层将有助于巩固你对神经网络内部结构的理解。因此,让我们手动并显式地创建这个神经网络,并实现前向传播和反向传播的逻辑,而不是使用深度学习库:
-
我们将首先导入
numpy以及来自 scikit-learn 库的方法,以加载示例数据集并执行数据分区:import numpy as np from sklearn import datasets from sklearn.model_selection import train_test_split -
接下来,我们定义 ReLU,这是使 MLP 非线性的方法:
def ReLU(x): return np.maximum(x, 0) -
现在,让我们部分定义初始化 MLP 模型所需的类,该模型有一个隐藏层和一个输出层,能够进行前向传播。层由权重表示,其中
w1是隐藏层的权重,w2是输出层的权重。此外,b1是输入层和隐藏层之间连接的偏置,b2是隐藏层和输出层之间连接的偏置:class MLP(object): def __init__( self, input_layer_size, hidden_layer_size, output_layer_size, seed=1234 ): rng = np.random.RandomState(seed) self.w1 = rng.normal( size=(input_layer_size, hidden_layer_size) ) self.b1 = np.zeros(hidden_layer_size) self.w2 = rng.normal( size=(hidden_layer_size, output_layer_size) ) self.b2 = np.zeros(output_layer_size) self.output_layer_size = output_layer_size self.hidden_layer_size = hidden_layer_size def forward_pass(self, x): z1 = np.dot(x, self.w1) + self.b1 a1 = ReLU(z1) z2 = np.dot(a1, self.w2) + self.b2 a2 = z2 return z1, a1, z2, a2 -
为了让 MLP 学习,我们将现在实现反向传播方法,生成偏置和权重的平均梯度:
def ReLU_gradient(x): return np.where(x > 0, 1, 0) def backward_pass(self, a0, z1, a1, z2, a2, y): number_of_samples = len(a2) average_gradient_w2 = ( np.dot(a1.T, (a2 - y)) * (2 / (number_of_samples * self.output_layer_size)) ) average_gradient_b2 = ( np.mean((a2 - y), axis=0) * (2 / self.output_layer_size) ) average_gradient_w1 = np.dot( a0.T, np.dot((a2 - y), self.w2.T) * ReLU_gradient(z1) ) * 2 / (number_of_samples * self.output_layer_size) average_gradient_b1 = np.mean( np.dot((a2 - y), self.w2.T) * ReLU_gradient(z1), axis=0 ) * 2 / self.output_layer_size return ( average_gradient_w2, average_gradient_b2, average_gradient_w1, average_gradient_b1 )请注意,ReLU 函数的导数是 f′(x) = 1 如果 x > 0,f′(x) = 0 如果 x <= 0。
-
对于最后一个类方法,我们将实现梯度下降步骤,利用反向传播得到的平均梯度,这是更新偏置和权重的过程:
def gradient_descent_step( self, learning_rate, average_gradient_w2, average_gradient_b2, average_gradient_w1, average_gradient_b1 ): self.w2 = self.w2 - learning_rate * average_gradient_w2 self.b2 = self.b2 - learning_rate * average_gradient_b2 self.w1 = self.w1 - learning_rate * average_gradient_w1 self.b1 = self.b1 - learning_rate * average_gradient_b1 -
现在我们已经手动创建了一个正确的 MLP 类,让我们设置数据集并尝试从中学习。MLP 的结构只允许使用表格结构的数据集,这些数据集既是单维的又是数值型的,因此我们将使用一个名为
diabetes的数据集,它包含 10 个数值特征,即年龄、性别、体重指数、平均血压和 6 项血清测量数据,作为输入数据,同时有一个衡量糖尿病疾病进展的定量指标作为目标数据。该数据集已经方便地保存在 scikit-learn 库中,因此让我们首先加载输入的 DataFrame:diabetes_data = datasets.load_diabetes(as_frame=True) diabetes_df = diabetes_data['data'] -
现在,我们将把 DataFrame 转换为 NumPy 数组值,以便准备好供神经网络使用:
X = diabetes_df.values -
加载数据的最后一步是从糖尿病数据中加载目标数据,并确保它有一个额外的外部维度,因为 PyTorch 模型以这种方式输出其预测结果:
target = np.expand_dims(diabetes_data['target'], 1) -
接下来,让我们将数据集分为 80% 用于训练,20% 用于验证:
X_train, X_val, y_train, y_val = train_test_split( X, target, test_size=0.20, random_state=42 ) -
现在,数据已经准备好,包括训练和评估分区,让我们从定义的类初始化一个 MLP 模型,包含一个 20 神经元的隐藏层和一个 1 神经元的输出层:
mlp_model = MLP( input_layer_size=len(diabetes_df.columns), hidden_layer_size=20, output_layer_size=target.shape[1] ) -
数据和模型准备好后,到了训练我们从头开始构建的模型的时候。由于数据集足够小,有 442 个样本,所以使用梯度下降法时没有运行时问题,因此我们将在这里使用完整的梯度下降进行 100 个周期。一个周期意味着对整个训练数据集进行一轮完整的训练:
iterations = 100 training_error_per_epoch = [] validation_error_per_epoch = [] for i in range(iterations): z1, a1, z2, a2 = mlp_model.forward_pass(X_train) ( average_gradient_w2, average_gradient_b2, average_gradient_w1, average_gradient_b1 ) = mlp_model.backward_pass(X_train, z1, a1, z2, a2, y_train) mlp_model.gradient_descent_step( learning_rate=0.1, average_gradient_w2=average_gradient_w2, average_gradient_b2=average_gradient_b2, average_gradient_w1=average_gradient_w1, average_gradient_b1=average_gradient_b1, ) _, _, _, a2_val = mlp_model.forward_pass(X_val) training_error_per_epoch.append(mean_squared_error(y_train, a2) validation_error_per_epoch.append( mean_squared_error(y_val, a2_val) ) -
让我们使用
matplotlib绘制训练集和验证集的均方误差:plt.figure(figsize=(10, 6)) plt.plot(training_error_per_epoch) plt.plot(validation_error_per_epoch, linestyle = 'dotted') plt.show()这将生成以下图表:

图 2.5 – 训练和验证集的均方误差与周期数的图
到此为止,你已经实现了一个 MLP,并且从头开始训练了它,而无需依赖深度学习框架!但是,我们的实现是否正确和合理?让我们在下一节验证这一点。
使用深度学习框架实现 MLP
深度学习框架旨在简化并加速深度学习模型的开发。它们提供了大量常用的神经网络层、优化器和通常用于构建神经网络模型的工具,并且提供了非常容易扩展的接口以实现新方法。反向传播本身被框架的用户抽象化,因为梯度会在后台自动计算,并在需要时使用。最重要的是,它们允许使用 GPU 来高效地进行模型训练和预测。
在本节中,我们将使用一个名为 PyTorch 的深度学习框架构建与上一节相同的 MLP 模型,并验证这两种实现是否产生相同的结果:
-
我们将从导入必要的库开始:
Import torch import torch.nn as nn import torch.nn.functional as F -
接下来,让我们定义包含两个全连接层的 MLP 类,并定义一个前向传播方法,该方法带有可以设置输入层大小、隐藏层大小和输出层大小的参数:
Class MLPPytorch(nn.Module): def __init__( self, input_layer_size, hidden_layer_size, output_layer_size ): super(Net, self).__init__() self.fc1 = nn.Linear(input_layer_size, hidden_layer_size) self.fc2 = nn.Linear(hidden_layer_size, output_layer_size) class MLPPytorch(nn.Module): def __init__( self, input_layer_size, hidden_layer_size, output_layer_size ): super(Net, self).__init__() self.fc1 = nn.Linear(input_layer_size, hidden_layer_size) self.fc2 = nn.Linear(hidden_layer_size, output_layer_size) def forward(self, x): x = F.relu(self.fc1(x)) x = self.fc2(x) return x -
你会注意到没有实现反向传播函数,这减少了定义神经网络模型所需的工作量。当你从
Pytorch模块类继承时,反向传播功能将自动提供,与您定义的Pytorch层一起使用。最后,让我们使用隐藏层大小为 10 以及根据糖尿病数据集的输入和输出大小来初始化 MLP:Net = MLPPytorch( input_layer_size=len(diabetes_df.columns), hidden_layer_size=10, output_layer_size=y_train.shape[1], ) -
现在,让我们检查一下使用
numpy变体的前向和反向传播功能。首先,让我们初始化PytorchMLP,并从基于numpy的 MLP 模型复制权重:with torch.no_grad(): net.fc1.weight.copy_( torch.from_numpy(mlp_model.w1.T) ) net.fc1.bias.copy_( torch.from_numpy(mlp_model.b1) ) net.fc2.weight.copy_( torch.from_numpy(mlp_model.w2.T) ) net.fc2.bias.copy_( torch.from_numpy(mlp_model.b2) ) -
现在,让我们将数据集准备为适合 PyTorch 模型使用的 Tensor 对象:
torch_input = torch.from_numpy(X_train) torch_target = torch.from_numpy(y_train) -
为了获得相同的梯度,我们必须使用相同的 MSE 损失并应用反向传播:
criterion = nn.MSELoss() output = net(torch_input.float()) loss = criterion(output, torch_target.float()) loss.backward() -
现在,让我们验证这两种实现的梯度:
np.testing.assert_almost_equal(output.detach().numpy(), a2, decimal=3) np.testing.assert_almost_equal(net.fc2.weight.grad.numpy(), average_gradient_w2.T, decimal=3) np.testing.assert_almost_equal(net.fc2.bias.grad.numpy(), average_gradient_b2, decimal=3) np.testing.assert_almost_equal(net.fc1.weight.grad.numpy(), average_gradient_w1.T, decimal=3) np.testing.assert_almost_equal(net.fc1.bias.grad.numpy(), average_gradient_b1, decimal=3)
这巩固了你对神经网络基础知识的理解,以及对多层感知器(MLP)架构的掌握,为你深入学习深度学习领域中的更高级概念做好准备。在我们进入下一节讨论更高级的神经网络之前,我们将先探讨正则化这一主题,最后再探讨如何设计一个具有实际应用案例的 MLP。
正则化
深度学习中的正则化已经发展到现在的阶段,指的是任何用于增加构建模型对外部数据的泛化能力的神经网络、数据或训练过程中的附加或修改。如今,所有高效能的神经网络都在架构中嵌入了某种形式的正则化方法。这些正则化方法中的一些会带来额外的有益副作用,比如加速训练过程或提升训练数据集上的表现。但最终,正则化的主要目标是提高泛化能力,换句话说,就是提升外部数据上的表现指标并减少错误。简要回顾一下,以下是一些常见的正则化方法:
-
Dropout 层:在训练过程中,按指定的概率随机删除所有神经节点的信息,通过将神经节点的输出替换为零,实质上使得信息无效。这减少了对任何单一节点/信息的过度依赖,并增加了泛化的概率。
-
L1/L2 正则化:这些方法向损失函数添加一个惩罚项,旨在减少模型为特征分配高权重的情况。L1 正则化,也称为 Lasso,使用权重的绝对值,而 L2 正则化,也称为 Ridge,使用权重的平方值。通过控制权重的大小,这些方法有助于防止过拟合并提高泛化能力。通常,这些方法应用于输入特征。
-
批量归一化层:这种方法在训练和推理阶段对外部数据进行标准化,通过将数据缩放到均值为零、标准差为一来实现。这是通过去除计算得到的均值并将其除以计算得到的标准差来完成的。均值和标准差通过小批量(基于模型确定的训练批量大小)在训练过程中进行计算并迭代更新。在推理阶段,会应用在训练过程中计算得到的最终学习运行均值和标准差。这有助于提高训练时间、训练稳定性和泛化能力。请注意,每个元素都有自己的均值和标准差。研究表明,批量归一化可以平滑损失函数的曲面,使得更容易达到最优值。
-
组归一化层:与每个元素在批次中的均值和标准差不同,组归一化按每个样本的组对数据进行标准化,每组有一个均值和一个标准差。可以配置组的数量。由于硬件限制,批量归一化在批次中样本数量较小时会降低性能。当每批的数据量非常小时,这一层优于批量归一化,因为均值和标准化的更新不依赖于批次。然而,在大批量数据下,批量归一化仍然优胜。
-
权重标准化:这将相同的标准化过程应用于神经网络的权重。神经网络的权重在训练后可能会增长到非常大的数字,这将导致输出值过大。这个方法的想法是,如果我们使用批量归一化层,输出值反正会被标准化,那为什么不退一步,将同样的过程应用于权重本身,确保在变成输出值之前,权重的值已经以某种形式被标准化呢?一些简单的基准测试表明,当与组归一化层结合使用时,在低批量大小下,它能取得比批量归一化在高批量大小设置下更好的性能。
-
随机深度:与其在训练阶段通过 dropout 概念上让神经网络使用更窄的层,随机深度则是在训练过程中减少网络的深度。这个正则化方法利用了 ResNet 中跳跃连接的概念,稍后会介绍,跳跃连接是指将前面层的输出额外传递到后面的层。在训练过程中,跳跃连接之间的层会被完全绕过,从而随机模拟一个更浅的网络。这个正则化器可以加速训练时间,并提高模型的泛化性能。
-
[0, 0, 0, 1]和[0.0001, 0.0001, 0.0001, 0.9999],分别表示。这个方法的想法是,我们不应该训练模型让它对结果过于自信,这会表明它对训练数据过拟合,并且无法推广到外部数据。该方法鼓励同一类别样本的最后一层输出更接近,而鼓励不同类别样本之间的输出保持相等的距离。此外,这也有助于缓解在标签不准确的样本中产生的过度自信。 -
数据增强:当原始数据不足以充分代表任何标签的所有变异时,数据增强有助于在计算上增加数据的变异性,从而用于训练。这通过模型能够学习更完整的数据变异性,来有效地提高泛化能力。这可以应用于任何数据类型,并将在 第八章 中进行更详细的介绍,探索 监督学习。
正则化是任何神经网络架构中的重要组成部分,在本章介绍的所有架构中都会看到它。当为特定问题选择正则化技术时,首先应考虑数据集的特性和你试图解决的问题。例如,如果你有一个小批量大小,群体归一化或权重标准化可能比批量归一化更适合。如果数据集的变化有限,数据增强可以用来提高泛化能力。选择这些技术时,可以从简单的正则化方法(如 dropout 或 L1/L2 正则化)开始,并评估其性能。然后,你可以单独或组合尝试其他技术,并比较它们对模型性能的影响。监控训练和验证指标非常重要,以确保所选的正则化方法不会导致过拟合或欠拟合。最终,正则化技术的选择取决于实验与验证的结合、领域知识以及对特定问题和数据集的理解。
接下来,让我们深入探讨 MLP(多层感知器)的设计。
设计 MLP
表格数据并不是神经网络最擅长的领域,而且往往在性能指标上,提升决策树的表现优于 MLP。然而,有时在某些数据集上,神经网络可能会超越提升树模型。在处理表格数据时,务必将 MLP 与其他非神经网络模型进行基准测试。
MLP 是最简单的神经网络形式,可以在两个维度上进行高层次的修改,这两个维度与所有神经网络类似:网络的宽度和网络的深度。从头开始构建标准 MLP 架构时,一个常见策略是从浅层和狭窄的宽度开始,获得一个小的基准后,再逐渐增加这两个维度。通常,对于表格数据上的 MLP,增加网络深度的性能收益在大约第四层时会趋于停滞。ReLU 是一种标准的激活层,已证明可以允许稳定的梯度和最佳的任务学习。然而,如果你有时间追求实际价值,考虑将激活层替换为更高级的激活层。目前,激活层研究领域非常细致,结果大多没有标准化,在不同的数据集上反馈混合,因此使用任何高级激活层并不能保证获得更好的性能。
MLP 的一种改进方法是使用一种叫做去噪自编码器的神经网络,生成可以作为 MLP 输入的去噪特征。这个进展将在稍后的第五章,《理解自编码器》中详细描述。训练方法与架构密切相关,旨在实现良好的性能。这些方法大多是通用的,不依赖于任何特定架构,因此将在第八章,《探索监督式深度学习》和第九章,《探索无监督深度学习》中分别介绍。
接下来,让我们总结一下本章的内容。
总结
MLP 是深度学习中基础性的架构组成部分,不仅限于处理表格数据,它也不再是一个被取代的旧架构。MLP 在当今许多先进的神经网络架构中非常常见,作为子组件用于提供更自动化的特征工程、减少大特征的维度,或者将特征形状调整为目标预测所需的形状。请留意接下来几章中将介绍的 MLP,或者更重要的是,全连接层!
深度学习框架提供的自动梯度计算简化了反向传播的实现,使我们能够专注于设计新的神经网络。确保这些网络中使用的数学函数是可微分的至关重要,尽管在采用成功的研究成果时通常会自动处理这一点。这就是开源研究与强大深度学习框架结合的魅力所在!
正则化是神经网络设计中的一个关键方面,虽然我们在本章中已详细讨论了它,但后续章节将展示它在不同架构中的应用,而无需进一步解释。
在下一章,我们将深入探讨另一种类型的神经网络,称为卷积神经网络,它特别适用于与图像相关的任务,并且有着广泛的应用。
第三章:理解卷积神经网络
MLP(多层感知器)结构设计用于处理一维数据,无法直接处理二维或更高维度的数据,除非进行预处理。一维数据也叫做表格数据,通常包括分类数据、数值数据,甚至可能是文本数据。二维数据或更高维度的数据通常是图像数据。当图像为灰度格式时,它是二维的;当图像有 RGB 层并且能较好地表现人类视觉时,它是三维的;而多于三维的图像则是高光谱图像。通常,为了让 MLP 能够处理图像,你需要将数据展平,并有效地以一维格式表示相同的数据。展平数据在某些情况下可能是可行的,但舍弃了定义图像的空间特征,丧失了捕捉该关系到目标的潜力。此外,展平数据在处理大图像时并不合适。图像数据的一个重要特征是,目标可以出现在图像的任何空间位置。简单的 MLP 对数据输入的位置高度依赖,无法适应图像中目标的不断变化的位置和方向。
这正是 卷积神经网络(CNN)层的强项,通常是处理图像数据时机器学习专家的首选方法。迄今为止,顶尖的卷积架构始终在图像数据集上超过 MLP 架构的性能。在本章中,我们将涵盖以下内容,并重点讨论 CNN:
-
理解卷积神经网络层
-
理解池化层
-
构建 CNN 架构
-
设计一个用于实际应用的 CNN 架构
-
探索 CNN 架构家族
技术要求
本章包括一些在 Python 编程语言中的实际实现。要完成这些任务,你需要一台安装了以下库的计算机:
-
pandas -
matplotlib -
seaborn -
scikit-learn -
numpy -
keras -
pytorch
本章的代码文件可在 GitHub 上找到:github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_2。
理解卷积神经网络层
现在,让我们专注于卷积层的基础知识,从 图 3.1 开始,它展示了卷积滤波器的操作过程。滤波器是一个小的权重矩阵,用于从输入数组中提取特征或模式。卷积滤波器是一种滑动图像的滤波器,执行卷积操作,通过计算点积来提取特征:

图 3.1 – 卷积滤波器在 Fashion MNIST 数据集中 T 恤图像上的操作
卷积层由多个大小相同的卷积滤波器构成。卷积滤波器是 CNN 中主要的模式检测器,每个滤波器都会学习识别图像中存在的多维模式。这些模式可以从低级模式,如线条和边缘,到中级模式,如圆形或方形,再到高级模式,如特定款式的 T 恤或鞋子,具体取决于卷积层在 CNN 中的深度。在 第十二章《解读神经网络》中,我们将探讨所学习的模式,并对这些模式进行定性和客观评估。
卷积滤波器可以是多维的,但对于普通图像来说,二维卷积滤波器更常用。核在 CNN 层中类似于滤波器。这些二维滤波器的权重数量与其大小相同,并在与输入图像数据相同大小的部分上进行点积操作,以得到一个单一的值;随后,这些值会加上一个偏置项。通过以滑动窗口的方式,按照定义的步幅,从上到下、从左到右系统地对同一操作进行处理,卷积滤波器将完成一次前向传播,得到一个维度较小的二维输出。在这里,步幅指的是滑动滤波器时每次移动的像素步数,最小步幅至少为 1 像素。此操作基于目标可能出现在图像中的任意空间位置,并在整个图像中使用相同的模式识别方法。
图 3.1 显示了一个二维卷积滤波器,大小为 5x5 像素,其中包含 25 个可学习的权重组件和一个偏置组件,以及一个 28x28 像素大小的 T 恤输入图像。滤波器的大小是可以配置为其他值的,通常范围在 1 到 7 之间,通常是正方形,但也可以设置为不规则的矩形形状。典型的滤波器大小值可能看起来太小,无法识别图像中预测 T 恤的高阶特征,但当多个滤波器按非周期性顺序一个接一个地应用时,操作结束时的滤波器会有更大部分图像的感受野。感受野是指卷积滤波器可以“看到”或响应的输入空间的区域。它决定了影响特定输出单元的输入的空间范围。为了捕捉低级特征,滤波器必须具有较小的感受野,而为了捕捉高级特征,滤波器必须具有较大的感受野。图 3.2 描述了三个滤波器按顺序应用时的这一概念:

图 3.2 – 第二个卷积滤波器的感受野大小为 3x3,尽管其卷积滤波器大小仅为 2x2
现在,从左到右、从上到下的滑动窗口只是一个很好地可视化过程的方法。然而,实际上,这个过程可以一次性并行处理,以利用 GPU 的并行处理能力。图 3.3 显示了一个 4x4 像素维度的卷积滤波器,步长为 4,在同一 28x28 T 恤图像上应用的所有窗口位置。这将导致 7x7 的数据输出大小:

图 3.3 – 在 28x28 图像上,使用 4x4 卷积滤波器,步长为 4 像素,进行所有窗口位置的操作
考虑一个 CNN 层,包含 16 个 5x5 像素的滤波器,步长为 1 像素。使用这种层配置对 T 恤图像进行前向传递时,输出的总像素数为 16x26x26(深度 x 宽度 x 高度)。在图像为 RGB 图像且有红、绿、蓝三个通道的情况下,由于卷积滤波器是二维的,相同的滤波器将以类似方式应用于三个通道,每个通道使用标准卷积层处理。对三个通道分别应用的滤波器产生的三维输出将被加总起来。卷积滤波器的数量将是输出数据通道的大小,并且将作为后续卷积层的输入通道大小。图 3.4展示了卷积滤波器的 3x3 输出在通道方向上的加和过程。请注意,偏置仅在每个滤波器跨越通道时添加一次,而不是按通道添加:

图 3.4 – 多通道 3x3 卷积滤波器输出的聚合
在前面的图中,Conv 是 卷积层 的缩写,并将在本章的其余部分作为简化图示的约定使用。由于了解神经网络的大小对于确保拥有足够的计算资源以容纳和处理网络是有益的,接下来我们也将计算该卷积层将包含的参数数量。参数的数量可以通过以下方式计算:
输入通道数 x 过滤器数量 x ((过滤器宽度 x 过滤器高度) + 1)
将相应的数字代入公式,你将得到 416 个参数。如果这些权重以浮动点 32(fp32)格式存储,则以字节为单位,意味着 416x32 位/8=1,664 字节。由于输出数据的大小为 16x26x26,在空间维度上的数据大小以非常缓慢的速度减少,且由于最终目标是将这些值减少到目标数据的大小,因此我们需要一些方法来减小数据的尺寸。这里引入了另一层,叫做池化层,来实现这一目标。
理解池化层
仅通过从图像的 CNN 层进行前向传播,二维输出数据的大小可能会减少,但仍然是一个相当大的尺寸。为了进一步减少数据的大小,使用了一种叫做池化层的层类型来战略性地聚合和整合值,同时保持有用的信息。可以将这个操作看作是一种图像调整大小的方法,同时尽可能保持更多的信息。该层没有学习的参数,主要是为了简化且有意义地减少输出数据。池化层通过应用类似滑动窗口滤波器的过程来工作,配置与卷积层相似,但它不是应用点积和加偏置,而是进行一种聚合。聚合函数可以是最大聚合、最小聚合或平均聚合。应用这些聚合的层分别称为最大池化、最小池化和平均池化。
考虑在第一个 CNN 层后应用一个滤波器大小为 4、步幅为 2 的平均池化层。经过一次前向传播,16x26x26 的输出将变成 16x12x12 的数据大小。这大大减少了数据的尺寸!
另一种类型的池化层应用全局聚合函数。这意味着数据的整个二维宽度和高度组件将被聚合成一个单一的值。这种池化层的变种通常被称为全局池化层。该层应用于将数据完全转化为一维结构,以便它可以与一维目标兼容。该层在keras库中直接可用,但在pytorch中则需要通过将池化滤波器的大小设置为与输入特征图的大小相同间接实现。
构建 CNN 架构
CNN 架构通常是通过一个个堆叠的概念逻辑块构建的。这些逻辑块的结构方式相同,具有相同类型的层和层连接,但在参数配置上可能有所不同,比如滤波器的大小、步幅、所使用的填充类型以及填充的数量。最简单的逻辑卷积块是卷积层、池化层和激活函数,按此顺序排列。填充是指在卷积操作后,为了保持输入图像的空间维度而在图像周围添加的额外像素。逻辑块是用来简洁而高效地描述和引用架构的一种方式。它们还使你能够以深度可扩展的方式构建 CNN 架构,而无需逐一创建和设置每个层的参数。深度与深度度相同,指的是神经网络层的数量。
可以根据目标是逐渐缩小特征图还是扩大特征图来设计参数。对于一维目标的情况,目标可能是将特征逐渐缩小为一维特征,以便将特征传递给全连接层。然后,这些全连接层可以进一步将(仍然很大的)维度映射到适合目标的维度。你可以在图 3.5中看到这样的架构的简单设计:

图 3.5 – 从零开始构建的简单 CNN 架构,同时遵循逻辑块类比。
在 pytorch 的代码中,这个例子看起来是这样的:
class ConvArch(nn.Module):
def __init__(self):
super(ConvArch, self).__init__()
self.conv1 = nn.Conv2d(
in_channels=1, out_channels=12, kernel_size=4,
stride=2, padding=0
)
self.conv2 = nn.Conv2d(
in_channels=12, out_channels=5, kernel_size=3,
stride=1, padding=0
)
self.fc2 = nn.Linear(5, 3)
def forward(self, x):
x = F.relu(
F.avg_pool2d(
self.conv1(x), kernel_size=3, stride=2
)
)
x = F.relu(
F.avg_pool2d(
self.conv2(x), kernel_size=3, stride=1
)
)
x = F.avg_pool2d(x, kernel_size=x.size()[2:])
x = self.fc2(x.reshape((x.size()[:2])))
return x
同样,CNN 架构的反向传播将由深度学习库自动处理。
我们构建的架构是基于基本的分类问题类型,这也证明了在网络末尾需要一个全连接网络的必要性;这通常被称为“头”。使用的卷积层逻辑块集被称为网络的“主干”。网络的头部可以根据问题类型替换为其他结构,但主干可以完全适配到大多数架构中,适用于任何问题类型,如目标检测、图像生成、图像描述或通过表征学习的图像识别。关于这些问题类型的讨论会在第八章,探索有监督深度学习中进一步展开。
现在我们已经成功手动创建了一个简单的 CNN,我们的理论已经与卷积网络的核心算法相契合并同步,接下来我们将能够轻松设计更先进的 CNN 主干架构。但在此之前,这就引出了一个问题:我们如何为我们的使用场景设计 CNN?
为实际使用设计 CNN 架构
对于实际的使用场景,CNN 不应像 MLP 那样设计。实际使用意味着目标不是为未探索的问题类型研究一个新的创新架构。今天,基于 CNN 已经取得了许多进展。这些进展通常有两种形式:
-
它设定了一个全新的基准,彻底重新设计了 CNN 架构的构建方式。
-
它是建立在现有基准 CNN 架构的基础上,同时补充和提升了基准架构的性能。
与多层感知机(MLP)的理想设计方法相比,卷积神经网络(CNN)的关键区别在于,应该使用已发布的 CNN 架构结构,而不是从零开始设计架构。CNN 架构的结构定义了层的类型以及不同类型的层如何连接;它们通常通过逻辑块实现。此外,特定结构的独特性定义了 CNN 架构的家族。新的 CNN 研究进展通常会以不同的尺寸配置出现,以便根据计算资源限制、运行时要求或数据集和问题复杂性选择合适的架构尺寸。与 MLP 设计类似,如果资源和运行时不是限制条件,在选择了 CNN 架构结构后,应该根据问题的复杂性和数据集的大小,从一个合理小的 CNN 开始。你可以逐渐测试更大的 CNN,看看当增加大小时,性能是否有所提升,反之如果性能下降。
不同的 CNN 架构结构或架构家族通常用于捕捉网络固有的不同架构问题。有些架构家族的设计方式是利用了更好的硬件资源,否则就无法执行这些架构。为了获得良好的性能,一个好的做法是在初始阶段多样化你使用的架构结构类型。选择具有相似浮点运算每秒(FLOP)的架构结构尺寸变体,并运行实验以获得性能得分,理想情况下,选择较小的尺寸以最大化探索效率。与模型的参数数量相比,考虑每秒浮点运算作为模型复杂度的指标更为相关;因为参数数量没有考虑模型的实际运行时,而模型的运行时可能会受益于并行化。一旦获得了这些数字,就选择顶尖的模型家族,并尝试更大的尺寸变体进行基准测试,以找到最适合你使用案例的模型变体。
大多数 CNN 的研究改进都基于一个简单的基线架构。这意味着对同一个基线架构所做的所有其他改进并没有一起进行基准测试。将这些改进一起测试往往是互补的,但有时也可能对模型的度量性能产生不利影响。迭代地基准测试不同的配置,可能是获得令人满意的性能提升的最系统、最扎实的方法。
研究人员如何评估他们的改进呢?为了设计出适用于实际场景的 CNN 架构,了解如何评估架构将帮助你逐步朝着可接受的度量性能目标前进。在第十章,《探索模型评估方法》中,我们将更详细地讨论评估策略。用于评估 CNN 改进的主要方法之一是通过在名为ImageNet的公开图像数据集上的 top-1 预测准确率,该数据集包含数百万张图像,并有多个类别。ImageNet被认为是一个高度复杂的案例,每个类别都有无限种可能的变体,从室内到室外,从真实数据到合成数据都有涵盖。
那么,我们如何判断改进是否具有价值呢?改进的目标通常是提高基于ImageNet数据集的 top-1 准确率,提升模型前向传递的效率,或是专门优化网络的训练时间。
然而,仅仅按照ImageNet上的 top-1 准确率性能改进对架构进行排名,这种评估方式是有偏的,因为在大多数情况下,模型的绝对排名在应用相同架构到不同的图像数据集时会有所不同。将其作为选择现成架构的起点是明智的,但一定要确保评估一些ImageNet上表现最佳的架构,以找出最适合的方案。此外,虽然ImageNet是通过手动努力构建的,包括查询搜索引擎并将候选图像通过 Amazon Mechanical Turk 进行验证,但它仍然包含一些标签噪声,这可能会混淆度量性能背后的含义。
至于提高模型前向传递效率的改进方向,通常是通过减少架构的参数数量、将计算密集型的逻辑组件分解成多个组件来减少操作量,或者用具有更高并行性潜力的层来替换原有层。在这一方向上进行的改进,通常会保持或提高在ImageNet验证数据集上的度量性能得分。这个方向的改进是针对一系列设计用于低资源边缘设备上运行的卷积神经网络(CNN)架构的主要关注点,我们稍后会深入探讨。
探索不同 CNN 架构家族以获得更好的指标性能的一个高效方法是,选择那些有公开实现的模型家族,且这些实现包括了在ImageNet上训练的预训练权重。通过使用预训练权重初始化你的架构,可以带来一系列好处,包括更快的训练、更少的过拟合,以及即使使用的数据集与预训练权重的训练数据集属于不同的问题子集,依然能提高泛化能力。这个过程叫做迁移学习,我们将在第八章《探索监督式深度学习》和第九章《探索无监督深度学习》中详细学习。
探索 CNN 架构家族
现在,我们不再回顾 CNN 多年来的发展历史,而是来看一下经过精心挑选的不同模型架构家族。这些架构家族的选择是基于它们之间有足够的差异和多样性。需要注意的一点是,神经网络正在以惊人的速度进步。考虑到这一点,本文将介绍的架构家族是确保今天依然相关的。此外,本文将只呈现架构家族中最重要的信息,将研究论文中的大量内容简化成简洁而充分的细节。
在深入探讨这个话题之前,另一个需要注意的事情是,数据集上的指标性能通常是不同架构之间的主要比较方法,因此请注意,模型的指标性能是通过训练方法和架构的集体贡献来实现的。训练方法包括与模型架构无直接关系的细节,如所使用的损失函数、数据增强策略和数据分辨率。这些主题将在第八章《探索监督式深度学习》中讲解。我们将在这里介绍的架构家族有ResNet、DenseNet、MobileNet、EfficientNet、ShuffleNet和MicroNet。
理解 ResNet 模型家族
ResNet 架构,始于 2015 年,是基于深度网络训练困难的前提提出的,并旨在解决这一问题。梯度消失是神经网络面临的一个广泛已知的问题,即网络越深,来自数据的信息越少。然而,普通的深度 CNN 架构并未出现梯度消失的问题,这通过验证梯度信息得到了证明。梯度消失的主要原因是我们使用了过多的激活函数,将数据压缩到非常小的值范围内。例如,sigmoid 函数就是一个典型,它将数据映射从 0 到 1。所以,如果不确定,建议在架构中使用 ReLU!
ResNet 的 Res 部分是以残差(residuals)一词命名的。其理念是,从残差中学习比从未经修改的特征图中学习要容易得多。残差是通过添加来自早期层到后期层的跳跃连接(skip connections)来实现的。通俗来说,这意味着将早期部分的特征图(而非拼接)添加到网络后期部分的特征图中。这种加法的结果创建了将由后续卷积层学习的残差,后续卷积层再次应用更多跳跃连接并创建更多残差。残差可以轻松地应用于任何具有不同配置的 CNN 架构,并且可以被视为对较旧基线架构的改进。然而,作者还提出了多种利用不同数量卷积层的跳跃连接的架构变体,包括 ResNet-18、ResNet-34、ResNet-50、ResNet-101 和 ResNet-152。ResNet 架构家族为残差网络的简单使用提供了一个模板,并最终使其成为研究新进展的最流行基线。实际的架构设计在这里没有正式展示,因为记住这些设计对于掌握 CNN 知识没有任何影响。相反,图 3.6 显示了单个逻辑块的残差计算:

图 3.6 – ResNet 模型家族中实际残差连接方法的示例
为总结整个基线 ResNet 尺寸变体,以下表格显示了所有不同尺寸变体的配置摘要:

图 3.7 – 基础 ResNet 不同尺寸变体
表格中标注的括号表示定义为逻辑块的串行卷积层的集合。ResNet 还额外做了一个改进,将多个基础逻辑块归为一个更高层次的类别,称为“层名称”(layer name)。不同尺寸变体中,层名称的分组数目是相同的。逻辑块和更高层次的分组方法仅仅是为了让你能够简单有效地描述和引用复杂的架构。逻辑块中的前两个数字,通过乘法连接,定义了卷积滤波器的大小;紧随其后的数字和逗号定义了滤波器的数量。
研究发现,跳跃连接可以使任何神经网络的损失景观更加平滑,从而使学习过程变得更加容易和稳定。这使得神经网络更容易收敛到最优解。图 3**.8 显示了没有跳跃连接的神经网络的损失景观,左侧地形起伏不平,充满了许多山丘和山谷;而右侧则是通过使用 ResNet 的变种添加跳跃连接后,地形变得平滑,且明显形成一个单一的山谷:

图 3.8 – 左侧为没有跳跃连接的损失景观,右侧为带有跳跃连接的损失景观
从表格中可以得出的一个显著信息是,从 ResNet-50 开始,该架构采用了 1x1 卷积滤波器。由于该滤波器仅在通道维度上操作一个单维滤波器权重,因此该操作相当于在通道维度上以窗口方式应用全连接层。由于全连接层本身就是一个网络,再加上卷积网络,这种操作通常被称为 网络中的网络。接下来,我们将探讨以 ResNet 架构为基础的不同改进。
改进 ResNets
如前所述,ResNet 被认为是一个模型家族,包含了许多不同的变种,不仅在规模上有所不同,还具有不同的架构改进。任何基于 ResNet 的 CNN 架构都属于这个模型家族。这里,我们提到了一些值得注意的架构进展,并根据 ResNet 提供了它们的主要改进描述;它们按年份排序:
-
ImageNet与 ResNet 各变种相比,即使在参数数量相同的情况下,仍有明显差异,通常也有优势。 -
挤压与激励网络 (SE-ResNet)(2017 年):由于二维卷积操作没有考虑通道之间的关系,仅仅考虑特征图的局部和空间(宽度和高度)信息,因此提出了一种利用通道关系的方法,该方法包括挤压模块和激励模块。这是一种可以重复应用于现有 CNN 架构多个部分的方法。图 3**.9 显示了该方法的结构,其中在宽度和高度维度上应用了全局平均池化,随后使用两个全连接网络进行特征缩放和恢复至相同的尺寸,以便将通道信息进行组合:

图 3.9 – 挤压与激励结构
该结构的缩放部分是值的相乘。当与 ResNet 结合使用时,架构被称为 SE-ResNet。
-
ResNet-D(2019 年):这一进展对架构参数进行了简单的调整,以提高度量性能,同时保持参数数量,尽管在 FLOP 规范略有增加。由于 ResNet 的某些路径使用步长为 2 的 1x1 卷积,舍弃了 3/4 的信息,而调整步幅大小以确保不会明确删除任何信息是其中的一种调整。他们还通过将 7x7 卷积变为三个串行的 3x3 卷积来减少计算负载。
-
ResNet-RS(2021 年):这一进展结合了 ResNet-D 和压缩激励网络,并使用依赖于网络大小的图像尺寸。然而,它的增长速度比 EfficientNets(稍后会介绍)慢。
理解 DenseNet 架构系列
DenseNet 是一种架构系列,最早于 2018 年初提出。这种架构基于跳跃连接的思想,类似于 ResNet 架构系列,但更加强大,意味着它使用大量的跳跃连接。这些架构系列中的跳跃连接不同之处在于它们使用串联而不是通过求和的残差连接。求和允许较早的信息直接编码到未来层的输出中,无需修改神经元的数量,尽管稍微降低了信息的优势,需要未来层学习解码这些信息。串联增加了架构的大小,因为你需要创建额外的神经元来容纳额外的信息,从而使模型能够处理原始数据。两者都提供了使用跳跃连接的类似优势。逻辑块称为稠密块,其中块中每个后续层通过特征图串联可以访问块中它之前所有层的所有输出。在这些块中,使用零填充以确保每层输出的空间尺寸保持不变,以便可以串联特征图。这种设置促进了同一块中层之间的大量特征重用,并允许模型参数数量保持不变,同时增加了模型的学习能力。对于所有块中的每个后续层,每个块中的过滤器数量均固定为称为增长率的常数值,因为需要一种结构化的方式来添加层,以避免指数增加通道数。以 32 个过滤器为常量,块中第二层的输入将是具有 32 个通道的特征图,块中第三层的输入将是 64 个并串联,依此类推。
为了创建一个完整的网络架构,多个密集块依次堆叠,中间插入单独的卷积层和池化层,以逐渐减少特征图的空间维度。图 3**.10 显示了 DenseNet 模型家族下四种不同 DenseNet 模型架构的网络结构:

图 3.10 – DenseNet 模型家族,其中“conv”对应于批量归一化、ReLU 和卷积层的顺序层
这使得 DenseNet 能够在 top-1 ImageNet 精度方面超越其前身网络架构。
理解 EfficientNet 架构家族
EfficientNet 于 2020 年推出,通过使用自动化 神经架构搜索 (NAS) 方法,创建了一个高效的小基础架构,并利用易于使用的复合缩放方法来缩放架构的深度、宽度和图像分辨率。神经架构搜索是以平衡 FLOPS 和精度的方式进行的。使用的 NAS 方法来源于另一项研究 MnasNet,将在第七章中详细介绍,深度神经架构搜索。
复合缩放方法简单且可以扩展到任何其他网络,尽管 ResNet-RS 表明,缩放分辨率较慢提供了更多的价值。深度、宽度和分辨率的缩放方法定义在以下方程式中,其中结果将与原始基础架构的参数相乘,以放大架构:
depth = α φ , width = β φ, resolution = γ φ
这里,φ 是可以根据需求调整为不同值的系数,其他变量是常数,应设置为优化某些内容,如我们改变系数时 FLOPS 增加率的常数。对于 EfficientNet,这些常数被限制为满足以下条件:
α ∙ β 2 ∙ γ 2 ≈ 2
这将限制系数的增加,使得 FLOPS 约增加 2 φ。EfficientNet 设置为 α = 1.2,β = 1.1,γ = 1.15。这一复合缩放策略使得可以创建七个 EfficientNet 模型,命名为 B0 到 B7。图 3**.11 显示了 EfficientNet-B0 的结构:

图 3.11 – EfficientNet-B0 架构结构
请注意,MBConv 也被称为反向残差块,将在稍后的 理解 MobileNetV2 部分中对其进行详细介绍。
EfficientNetV2,于 2021 年发布,发现大图像分辨率会减慢训练时间,其中深度卷积在早期层较慢,且同时扩展深度、宽度和分辨率并非最优。EfficientNetV2 还使用了 NAS(神经架构搜索)来找到基础架构,但对 MBConv 块进行了修改,增加了更多的参数和操作,原因是它在某些情况下可以更快,具体取决于输入和输出数据的形状、它们在整个架构中的位置以及数据如何传输到计算处理器。稍后我们正式介绍 MBConv 时会详细解释这一点。EfficientNetV2 还使用了原始的复合缩放方法,但通过将最大图像分辨率设置为 480 并在架构的最后几个阶段添加额外层来提高网络容量。还增加了一种新的训练方法来减少训练时间;我们将在下一章详细讨论这些内容。这些改进使得四种不同的 EfficientNetV2 模型诞生,分别为EfficientNetV2-S、EfficientNetV2-M、EfficientNetV2-L和EfficientNetV2-XL。与原始的 EfficientNet 在相似 FLOP 值下,这些模型的 top-1 准确率超越了原有的 EfficientNet:

图 3.12 – EfficientNetV2-S 架构结构
EfficientNetV2-S 作为基础架构,类似于 EfficientNetB0 作为基础架构,架构结构如图 3.12所示。
了解适用于小型边缘设备的小型和快速 CNN 架构系列
一类明确的架构组在 CNN 架构的世界中占有一席之地,这些架构并非为了可扩展性或打破ImageNet基准而设计,而是为了小型设备而建造。小型设备通常被称为边缘设备,因为它们足够小巧和紧凑,可以移动,或可以物理部署并具备数据处理能力,数据就是在其产生的地方进行处理的。我们的智能手机就是能够生成图像的移动设备的例子。不是移动设备的边缘设备包括闭路电视视频摄像头和门铃摄像头。将模型部署到边缘的关键好处如下所示:
-
减少通信延迟:与简单的数值或类别数据相比,图像和实时视频流的数据量较大。通过在边缘进行计算,减少了需要传输到集中式服务器的数据量,从而减少了数据传输所需的时间。有时,当计算在边缘进行时,可以完全消除传输需求,从而显著简化系统。
-
减少带宽需求:当图像在产生的地方进行处理时,只需返回简单的数据格式,如数值型或类别型数据,从而避免了对高带宽设备的需求。
-
增加冗余:集中式服务器意味着单点故障。将处理分配到各个边缘设备可以确保任何一个设备的故障不会影响整个系统。
针对边缘设备的小型 CNN 架构通常在没有单一显著基准的情况下设计整个模型结构,因此没有一个合适的模型家族来对这些架构进行分类。为了方便引用这些专为此目的设计的架构,建议将以下架构视为面向边缘的架构。还有其他可以应用于整个架构的技术,用以进一步优化模型的效率,我们将在第十五章《在生产中部署深度学习模型》中进行探讨,但目前我们先来看看其中的两个架构,即SqueezeNet和MobileNet。
理解 SqueezeNet
SqueezeNet 于 2016 年开发,旨在构建小型且快速的 CNN,并具备前一节所描述的优势,同时强调部署到内存有限的硬件上。可以采用的三种策略如下:
-
对于一个具有 3x3 滤波器的卷积层,部分使用 1x1 滤波器来替代。这意味着 1x1 滤波器和 3x3 滤波器在理论上可以共存于一个单独的层,并应用于相同的输入。然而,具体实现会有所不同,因为 1x1 滤波器和 3x3 滤波器会在同一输入上应用并行分支路径。这被称为扩展层(expand layer)。
-
通过使用少量 1x1 滤波器来减少传递到并行 1x1 和 3x3 滤波器的数据输入通道数量。这被称为 squeeze 层。
-
在架构的后期阶段对特征图进行下采样,以便大多数卷积层可以访问基于发现的较大特征图,从而提高指标性能。通过在早期阶段较少使用步幅为 2 像素的池化层,而在后期阶段及较大间隔时更多使用,从而实现这一点。在每个卷积层后并不总是使用池化层。
创建了一个逻辑块叫做 Fire,它提供了便于创建多个模块以构建架构的功能。该模块使得在 squeeze 层中配置 1x1 大小滤波器的数量、在扩展层中的 1x1 滤波器和 3x3 滤波器成为可能。这在图 3.13中有所展示。请注意,针对 3x3 卷积层的输出应用了填充操作,以确保其可以与扩展层中 1x1 卷积层的输出进行连接:

图 3.13 – 火焰模块/逻辑块
使用了八个Fire模块构建了一个叫做 SqueezeNet 的架构,它保持了从历史架构AlexNet(本书中未介绍,因为如今已不再实际使用)中传承下来的ImageNet性能,同时体积缩小了 50 倍。
理解 MobileNet
第一个 MobileNet 版本,称为 MobileNetV1,于 2017 年推出,专为移动和嵌入式设备设计,重点优化延迟问题,并通过这一过程使得网络尺寸较小,而不是反过来。
在整个架构中,除了第一层之外,深度可分离卷积层被广泛使用,这一版本的 MobileNet 包含 28 层。该层通过将标准卷积层分解为两层——深度卷积层和点卷积层——来构建。这种两层的设计理念是,使用标准卷积滤波器计算代价较高。深度卷积层为每个输入通道使用一个独特的滤波器,每个输入通道只有一个滤波器。这种每个通道一个滤波器的设计,使得ImageNet中只有 1%的独特案例。该分解也使得参数数量减少了约 5 倍。这一过程本质上是一种因式分解。深度卷积逻辑模块如下图所示:

图 3.14 – 深度可分离卷积层作为逻辑模块
MobileNet 还提供了两个可配置的参数,用以减少模型的大小和计算需求,同时在一定程度上牺牲性能。第一个是宽度倍增器,用于配置整个架构中的输入和输出通道。第二个是介于 0 和 1 之间的分辨率倍增器,适用于原始的 224x224 图像尺寸,当图像输入网络时可减小输入和输出特征图的尺寸。如果需要更快的运行时,这些参数还可以适配其他架构。图 3.15显示了 MobileNetV1 架构的结构和层配置:

图 3.15 – MobileNetV1 架构
MobileNet 被认为是一个独特的模型家族,基于第一个版本进行了两项改进。它们分别是 MobileNetv2 和 MobileNetv3-small,二者都在 2019 年推出。
理解 MobileNetV2
在我们深入了解 MobileNetV2 之前,先定义一下瓶颈层是什么,这是该进展中使用的核心思想。瓶颈层通常是一个输出特征图较少的层,且相对于前后层有较少的输出。MobileNetV2 建立在瓶颈层是信息所在的思想上;非线性激活会在瓶颈层破坏过多的信息,因此应用了线性层,最终通过捷径连接对瓶颈层进行残差运算。MobileNetV2 基于深度可分离卷积构建,添加了没有 ReLU 的线性瓶颈层,并在瓶颈层上应用残差。这一构建模块如下图所示。它被称为瓶颈反向残差块:

图 3.16 – MobileNetV2 的瓶颈反向残差块,也称为 MBConv
至于整个网络架构,第一版 MobileNet 中的所有深度可分离层都被新模块替换,唯一没有替换的是第一个具有 3x3 卷积核和 32 个滤波器的卷积层,如图 3.16所示。一个小的额外细节是,它们使用了ReLU6激活函数,这在低精度计算中表现良好。MobileNetV2 架构使用所示的逻辑块来创建多个具有不同设置的重复层块。这一架构使得在相同 FLOPs 下,ImageNet 上的性能曲线比 MobileNetV2 提高了约 5%到 10%。请记住,EfficientNetV2 使用了这个块,并且还有一个版本的块将线性瓶颈层和过滤层重新融合在一起。使用两个层而不是一个层的目的是减少所需的操作次数,但对于边缘设备来说,由于内存访问的瓶颈,实际的延迟可能会有所不同。有时,使用融合版本可能会在具有更多参数的情况下提高运行时速度,从而能从中学习更多信息。
了解 MobileNetV3-small
对于MobileNetV3-small,对 MobileNetV2 进行了几处修改:
-
它使用了一种更先进的非线性函数,称为
ImageNet。 -
在初始层和最后几层,计算量被进一步减少。
-
对于第一层,如图 3.15所示,32 个滤波器被减少到 16 个,并使用了硬切换非线性函数,实现了 2 毫秒的运行时间和 1000 万 FLOPS 的节省。
-
对于最后几层,最后一个 1x1 的瓶颈卷积层被移至最终平均池化层之后,同时前面的瓶颈(1x1)层和过滤(3x3)层也被移除。
-
它使用了一种平台感知的网络架构搜索的修改版本,称为 Mnasnet,以及一种称为 NetAdapt 的后搜索层减少方法来减少延迟,自动找到基于 MobileNetV1、MobileNetV2 以及 squeeze 和 excitation 网络的优化架构,同时考虑延迟和精度表现。NetAdapt 和 MnasNet 会在 第七章,深度神经 架构搜索 中介绍。
MobileNetV3-small 最终在相同的参数和 FLOPS 下,取得了比 MobileNetV2 更高的 top-1 ImageNet 精度。
了解 ShuffleNet 架构系列
ShuffleNet 有两个版本,ShuffleNetV1 和 ShuffleNetV2,我们将分别讨论这两个版本。
ShuffleNetV1,发布于 2017 年,重用了已知的卷积变种——组卷积,其中每个卷积滤波器仅负责输入数据通道的一个子集。MobileNet 通过为每个通道使用一个滤波器,采用了这一特殊的变种。组卷积通过仅在输入通道特征的小子集上操作,节省了计算成本。然而,当多个组卷积层依次堆叠时,通道间的信息不会相互作用——最终导致精度下降。ShuffleNetV1 使用通道洗牌操作,在多个组卷积层堆叠之间手动传递信息,而不牺牲 FLOPs。这使得网络既高效又小巧。
ShuffleNetV2,发布于 2018 年,基于 ShuffleNetV1,并专注于在实际应用中架构的运行时效率,同时考虑诸如内存访问成本、数据输入输出(I/O)以及网络并行度等因素。以下四种设计策略被用于打造这一新架构:
-
从输入到输出保持相等的通道宽度,以最小化内存访问成本。
-
降低组卷积以最小化内存访问成本。
-
减少网络碎片化以增加并行性。例如,单个构建模块中的卷积和池化操作的数量。
-
减少诸如 ReLU 等逐元素操作,因为它们有较高的内存访问成本:

图 3.17 – 左侧为 ShuffleNetV1 的两个构建模块,右侧为 ShuffleNetV2 的两个构建模块
在 图 3.17 中,左侧的前两个结构显示了 ShuffleNetV1 的两个构建模块,而右侧的最后两个结构则显示了 ShuffleNetV2 的两个构建模块。
了解 MicroNet,这是当前边缘计算领域的最先进架构
创建于 2021 年的 MicroNet,是在延迟和可实现的 top-1 ImageNet 精度表现方面的当前最先进技术,适用于 400 万到 2100 万 FLOP 范围内。MicroNet 的创新性体现在两个方面:
-
它引入了来自 MobileNet 的瓶颈/逐点卷积层和深度卷积层的因式分解版本,称为微因式分解卷积,其方式是减少输入数据到输出数据的连接/路径数量。这是通过使用多个分组卷积和一些膨胀卷积来实现的。膨胀卷积仅仅是内核中具有固定间距的卷积。将这些技术视为一种稀疏计算,只计算最需要的部分,以确保最小的输入到输出路径冗余。
-
它引入了一种新的激活函数,称为动态 shift-max,该函数利用分组卷积的输出,通过应用更高阶的非线性(两次),同时增强组间的连接。这是通过使用挤压和激励块的分组输出(每个通道生成一个单一值)作为加权机制,来获得基于组加权和的最大值。将此视为对 ShuffleNet 中通道洗牌的改进。图 3**.18 显示了动态 shift-max 操作结构的示意图,展示了一个来自四个组、使用分组卷积操作的 12 通道的单一示例特征图:

图 3.18 – 动态 shift-max 一般操作流程
MicroNet 利用了 ShuffleNet(通道洗牌机制)、ResNet(跳跃连接)、SENet(挤压和激励网络)和 MobileNet(从已因式分解的卷积中创建因式分解版本)等概念,并在其创新之上,创造了通过聚焦稀疏性概念和高效信息流改进的高效网络。该网络的具体细节可能令人不知所措,坦白说,也很难理解,因此此处提供的信息并不包含所有细节:

图 3.19 – 三个逻辑块的示意图,这些块被称为微块,用于构建不同大小的 MicroNet 变体
然而,图 3**.19 显示了逻辑块如何在今天最先进的网络中,基于相同的思想构建不同规模的网络。
总结用于边缘计算的 CNN 架构
为了总结边缘计算的架构,现在你已经拥有了专家在该领域中用来构建高度有效的 CNN 架构的直观知识,这些架构相比今天的大型模型,如 GPT-3,能够以极小的占用空间运行。下图展示了不同架构家族在边缘计算中的总体 top-1 ImageNet 精度表现与 FLOPS 图:

图 3.20 – 低于 4 亿 FLOPS 的边缘架构系列的 top-1 准确率性能
对这些结果应保持谨慎态度;模型之间的训练策略可能不同,会显著影响可实现的ImageNet top-1 准确率,同时在不同模型运行的随机初始化之间可能会得到不同的结果。此外,延迟并不完全由参数数量或 FLOPS 直接表示,还受到各个操作的内存访问成本、I/O 访问成本以及不同操作的并行程度的影响。
总结一下,图 3.21 显示了本章介绍的所有 CNN 模型系列在 FLOPS 基础上的整体性能图:

图 3.21 – 基于 FLOPS 的 ImageNet top-1 准确率的整体 CNN 模型系列性能
再次强调,我们应对这里呈现的结果保持谨慎,因为在ImageNet数据集上执行的训练技术并未在不同的基准测试中完全标准化。训练技术的差异可能导致结果差异巨大,且将在第八章,《探索监督深度学习》和第九章,《探索无监督深度学习》中更为详细地讨论。另一个重要的注意点是,尽管ImageNet被认为是一个足够大的图像数据集,可以作为基准使用,但由于数据本身在某些情况下已被证明存在噪声标签和系统性错误,保持一定的怀疑态度是必要的。ImageNet的修正版ImageNet Real已经发布,但并非所有模型都以此为基准进行测试或预训练。为了 100%确定哪种架构在特定数据集上预训练后表现更好,请在你的数据集上进行训练!此外,FLOPS 指标并不能完全代表模型的实际延迟,实际延迟可能会因代码结构、模型如何分布在多个设备上、可用的 GPU 或 CPU 数量,以及模型架构的并行程度而产生较大差异。
概述
CNN 是捕捉图像数据模式的首选模型。本章介绍的精选架构是核心骨干网络,可以作为基础,进一步用于解决更多定制化的下游任务,如图像目标检测和图像生成。
本章涵盖的卷积神经网络(CNN)将在后续章节中作为基础,帮助你学习其他基于深度学习的知识。请慢慢消化,并查看本书 GitHub 仓库中离线实现的深度学习库中不同架构的实现;我们这里不会呈现实际的实现代码。既然我们已经详细讲解了中低级的卷积神经网络,接下来我们将切换话题,探讨递归神经网络。
第四章:理解递归神经网络
递归神经网络 (RNN) 是一种设计用来处理顺序数据的神经网络,能够意识到数据的顺序。顺序数据可以包括基于时间的序列数据,也可以是具有顺序但没有时间成分的数据,例如文本数据。这类神经网络的应用基于数据本身的性质。对于时间序列数据,这可以用于现在预测(利用过去和当前数据进行的预测)或目标预测。对于文本数据,可以用于语音识别和机器翻译等应用。
随着能够捕获序列数据并完全移除递归连接的神经网络(如 Transformer)的出现,过去几年递归神经网络的研究有所放缓,并且取得了更好的性能。然而,RNN 在今天的实际应用中仍然广泛使用,作为一个良好的基准或仅仅是一个替代模型,因为它们的计算量较少,内存需求低,并且具有合理的度量性能,适用于更快速的计算。
最突出的两种 RNN 层是 长短期记忆 (LSTM) 和 门控递归单元 (GRU) 。本书不会介绍原始的递归神经网络,而是通过 LSTM 和 GRU 作为复习。LSTM 和 GRU 的主要操作提供了一种机制,仅保留相关记忆并忽略无用的数据,这是为时间序列或顺序数据精心设计的关键归纳偏差。
在本章中,我们将更深入地探讨这两种 RNN 网络。具体来说,我们将涵盖以下主题:
-
理解 LSTM
-
理解 GRU
-
理解标准 GRU 和 LSTM 层的进展
技术要求
本章内容简洁明了,但仍涵盖了 Pytorch 库中一些实际的实现。
你可以在 GitHub 上找到本章的代码文件,链接地址为 github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_4。
理解 LSTM
LSTM 于 1997 年发明,但仍然是广泛采用的神经网络。LSTM 使用 tanh 激活函数,因为它提供了非线性特性,同时提供了可以在更长序列中保留的二阶导数。tanh 函数有助于防止梯度爆炸和梯度消失。LSTM 层使用一系列顺序连接的 LSTM 单元。让我们深入了解 图 4.1 中 LSTM 单元的样子。

图 4.1 – 在一系列 LSTM 单元中的 LSTM 单元的视觉深度解析,形成了一个 LSTM 层
左侧的第一个 LSTM 单元展示了 LSTM 单元的高级结构,左侧的第二个 LSTM 单元展示了 LSTM 单元的中级操作、连接和结构,而右侧的第三个单元则是另一个 LSTM 单元,强调了 LSTM 层是由多个 LSTM 单元按顺序连接而成的。可以将 LSTM 单元视为包含四个门控机制,它们提供了忘记、学习、记住和使用序列数据的方式。你可能会好奇的一个显著问题是,为什么 sigmoid 在三条路径中被显示为一个独立的过程,这些路径从输入经过隐藏状态,到达遗忘门、记忆门和使用门。图 4**.1 中展示的结构是 LSTM 单元的经典图示,但没有包含关于连接权重的信息。这是因为输入通过加权求和的过程,将前一个单元的隐藏状态和当前序列数据结合起来,每一条连接都有不同的权重集。图 4**.2 显示了单个 LSTM 单元的最终低级结构,考虑了权重因素:

图 4.2 – LSTM 单元的低级结构
在图 4**.2中,W 和 B 分别代表权重和偏置。两个小写字母分别代表数据类型和门控机制。数据类型分为两类——由 h 表示的隐藏状态和由 i 表示的输入数据。涉及的门控机制包括由 F 表示的遗忘机制、由 L 表示的学习机制(学习机制涉及两个权重和偏置),以及由 U 表示的使用机制。为了正确理解一个 LSTM 单元有多少个参数,我们还需要解码隐藏状态和输入状态权重向量的维度。为了方便起见,我们假设输入向量的大小为 n,隐藏状态的大小为 m。
隐藏状态权重的维度为:
nm
而输入状态权重的维度为:
n²
另一方面,偏置是输入向量的大小。对于 Tensorflow 和使用 Tensorflow 的 Keras,偏置每个机制只添加一次。对于 PyTorch,偏置对于每个隐藏状态和输入状态权重都会添加。对于 PyTorch,偏置的参数数量可以定义为:
2n
由于有四个机制,如图 4**.2所示,这意味着在 PyTorch 实现中,LSTM 的参数数量可以根据以下公式计算:
参数数量 = 4(nm + n² + 2n)
现在我们了解了实际参数在单元中的位置,接下来我们深入探讨这些门控机制。
解码 LSTM 的遗忘机制
遗忘机制通过使用 sigmoid 激活函数与前一个单元状态相乘来实现。该门机制的名称暗示它基于当前输入序列和前一个单元输出的组合来确定需要删除的信息。可以这样理解:在0到1的范围内,过去的信息有多相关?sigmoid 机制强制将该范围限定在0到1之间。接近0的值会更多地遗忘前一个单元状态(长期记忆),而接近1的值则会更少遗忘前一个单元状态的记忆。
解码 LSTM 的学习机制
学习机制通过结合前一个单元输出的 sigmoid 激活函数和tanh激活函数,将遗忘门的输出与使用门的输出相乘,应用于当前输入序列的输出。这种机制也被称为输入门。该机制允许从当前输入序列中学习信息。学习到的信息随后会传递给记忆机制。此外,学习到的信息还会传递到下一个 LSTM 单元,用于信息使用的机制中。这两个机制将会依次介绍。
解码 LSTM 的记忆机制
记忆机制通过将遗忘过程剩余的信息与已学习的信息相加来实现,这就是学习门的输出。该门的输出将被视为 LSTM 单元的当前单元状态。单元状态包含所谓的 LSTM 序列的长期记忆。可以简单地将该机制视为一个操作,允许网络选择性地决定保留和记住输入的哪些部分。
解码 LSTM 的“信息使用”机制
信息使用机制通过在当前单元状态上应用 tanh 激活函数进行非线性处理,利用当前输入序列和前一个单元输出作为加权机制,决定应使用多少来自过去和现在的相关信息。应用使用门的输出将得到隐藏状态,这个状态也将作为下一个 LSTM 单元的前一个单元输出。
构建完整的 LSTM 网络
通常,要创建一个完整的 LSTM 网络,会将多个 LSTM 层连接在一起,使用多个 LSTM 单元的隐藏状态序列作为后续序列数据,应用到下一个 LSTM 层。在经过几个 LSTM 层后,前一层的隐藏状态序列通常会传递到一个全连接层,形成基于监督学习的简单 LSTM 架构的基础。图 4.3 显示了如何实现这一点的可视化结构:

图 4.3 – 一个简单的 LSTM 网络,包含两个 LSTM 层,并将其输入到一个全连接层
基于图 4**.3中描绘的网络,PyTorch 中的实现将如下所示:
-
首先,我们需要导入 PyTorch 库中的
nn模块:import torch.nn as nn -
现在,我们将基于图 4**.3定义网络架构,这次使用顺序 API 而不是类方法:
RNN = nn.Sequential( nn.LSTM( input_size=10, hidden_size=20, num_layers=2, dropout=0, ), nn.Linear(in_features=10, out_features=10), nn.Softmax(), ) -
LSTM 的输入大小、隐藏层大小和层数,以及线性层的输出特征大小,可以根据输入数据集和需求进行配置。注意,每个时间步或序列步的输入数据的大小可以大于一。这使得我们能够轻松地将原始特征映射到更具代表性的特征嵌入,并利用它们的描述能力。此外,通过将
dropout参数设置为 0 到 1 之间的值,可以轻松添加 dropout 正则化器,这将在每层(除了最后一层)以指定的概率引入 dropout 层。在步骤 2中定义的pytorch中的 RNN 现在可以像任何在类中定义的 PyTorch 模型一样进行训练。
和往常一样,PyTorch 让构建 RNN 变得更加简单和快捷。接下来,我们将介绍另一种类型的 RNN,称为门控 递归单元。
理解 GRU
门控递归 单元(GRU)是在 2014 年发明的,基于 LSTM 中实现的思想。GRU 旨在简化 LSTM,并提供一种更快速、更高效的方式,实现与 LSTM 相同的目标——根据过去和现在的数据自适应地记住和遗忘。在学习能力和可达的性能指标方面,二者没有明显的“银弹”赢家,通常在行业中,二者会相互对比,找出哪个方法提供更好的性能水平。图 4**.4展示了 GRU 的结构。

图 4.4 – GRU 的低级表示
图 4**.4采用与图 4**.2中 LSTM 相同的权重和偏置符号表示。这里有三种不同的小写字母符号表示。R 代表重置门,z 代表更新门,h 代表用于获得下一个隐藏状态的权重。这意味着 GRU 单元的参数比 LSTM 单元少,具有三组权重和偏置,而不是四组。这样,GRU 网络比 LSTM 网络稍微更快。
虽然通常描述为一个单元,但需要多个 LSTM 单元依次连接的相同理论也适用于 GRU:GRU 网络层将有多个 GRU 单元依次连接在一起。GRU 只包含两个机制,称为重置门和更新门,并且只有来自前一个 GRU 单元的一个输入,以及一个输出到下一个 GRU 单元。这个单一的输入-输出明显比 LSTM 更高效,因为我们需要进行的操作更少。现在,让我们深入了解这两个机制。
解码 GRU 的重置门
GRU 的重置门作为一种机制,用于忘记先前单元的长期信息,也称为隐藏状态。这个机制的目标类似于 LSTM 单元中的忘记门。同样,它将在 0 到 1 的范围内,根据当前输入序列和先前单元状态,决定我们应该减少和删除多少先前获得的长期信息。
然而,GRU 的重置门在功能上与 LSTM 的忘记门不同。LSTM 的忘记门决定从长期记忆中忘记哪些信息,而 GRU 的重置门决定忘记多少先前的隐藏状态。
解码 GRU 的更新门
GRU 的更新门控制从长期记忆中传递到当前保持的记忆的信息量。这类似于 LSTM 中的记忆门,帮助网络记住长期信息。与前一个单元的隐藏单元相关的每个权重将学习捕捉短期依赖和长期依赖。短期依赖通常会有接近0的重置门输出值,更频繁地遗忘先前的信息,而长期依赖则通过学习长期依赖的权重和隐藏状态位置来进行反向操作。
在与 LSTM 的记忆门的差异方面,LSTM 的记忆门决定了从当前输入和先前的隐藏状态中记住哪些信息,而 GRU 的更新门决定了记住多少先前的隐藏状态。
GRU 是一种简单的 RNN,相比 LSTM 具有更高效的操作。现在我们已经解码了 LSTM 和 GRU,接下来,我们不再重复类似 LSTM 的仅含 GRU 的完整网络,而是探索如何在这两种方法的基础上进行改进。
理解相较于标准 GRU 和 LSTM 层的进展
GRU 和 LSTM 是当前最广泛使用的 RNN 方法,但有人可能会想知道如何突破标准 GRU 或标准 LSTM 能够达到的极限。构建这种直觉的一个好方法是理解这两种层类型都能够接受序列数据,而要构建一个网络,你需要多个 RNN 层。这意味着完全可以在同一个网络中结合 GRU 和 LSTM 层。然而,这并不足以被视为一种突破,因为完全的 LSTM 网络或完全的 GRU 网络随时都可以超过 GRU 和 LSTM 组合网络的性能。让我们深入了解一下可以在这些标准 RNN 层上做的另一个简单改进,叫做双向 RNN。
解码双向 RNN
GRU 和 LSTM 都依赖于数据的顺序特性。这个顺序可以是时间步递增的前向序列,也可以是时间步递减的后向序列。选择使用哪个方向通常取决于试验和错误,往往自然的选择是使用前向时间顺序。
在 1997 年,出现了一种改进叫做双向 RNN(Bidirectional RNNs),它结合了前向 RNN 和后向 RNN,旨在最大化 RNN 模型能够处理的输入数据。原始的想法是利用未来信息和历史信息来估算当前时间步的值,因为未来和历史信息是可以获取的,两个 RNN 分别处理不同的数据集。这样自然就能够在这种数据设置下实现更好的预测性能。如今,这一想法已经扩展为一个通用的层,应用于相同的序列数据估计,并且也证明能够提供预测性能的提升。图 4.5 展示了使用 GRU 的双向 RNN 的一个例子,其中来自前向和后向 GRU 的隐藏状态被连接在一起:

图 4.5 – 双向 GRU
连接后的隐藏状态可以传递给全连接层,进行标准的监督学习目标。以下是一个在 PyTorch 中实现双向 GRU 的示例:
RNN = nn.Sequential(
nn.GRU(
input_size=10, hidden_size=20,
num_layers=2, bidirectional=True
),
nn.Linear(in_features=10, out_features=10),
nn.Softmax(),
)
接下来,让我们探索一下基于 LSTM 做出的一个改进。
向 LSTM 添加窥视孔(Peepholes)
2000 年引入的窥视孔使得细胞状态(来自前一个和当前的细胞),即 LSTM 中的长期记忆,能够影响 LSTM 单元中的 sigmoid 门控机制。直觉是,长期记忆包含了过去时间步的信息,而这些信息在前一个单元的隐藏状态中不可用的短期记忆中无法获取。这使得与普通 LSTM 相比,预测性能得到了改善。图 4.6 展示了来自细胞状态的额外窥视孔连接:

图 4.6 – LSTM 窥视孔连接
然而,这种方法的一个陷阱是,由于状态的长期记忆特性,单元状态可能会随着时间的推移增长到非常大的值,因为其没有边界。这可能会导致门控机制饱和,始终处于打开状态,从而使门控机制有时失效。这就引出了我们将在下一小节讨论的最后一种改进。
为了超越 LSTM 的窥视孔连接限制,加入工作记忆
2021 年,LSTM 的窥视孔连接上进行了改进,通过使用 tanh 激活函数来对单元状态施加边界,从而对单元状态进行限制。这一简单的改进在实验基准测试中表现出了比未加边界的 LSTM 更好的性能,被称为 LSTM 的工作记忆连接。图 4.7 显示了 LSTM 结构中的工作记忆连接:

图 4.7 – LSTM 结构中的工作记忆连接
RNN 与标准的 MLP 类似,意思是没有一个通用的数据集可以作为不同研究项目中的参考数据集。即使使用相同的数据集,结果也可能不是最终的真理来源,因为数据集并不广泛,无法泛化到其他数据集。换句话说,在序列数据中没有类似 ImageNet 的通用数据集。文本数据、视频数据和其他时间序列数据之间差异很大,但本质上都被视为序列数据,并且可以输入到 RNN 中。无论是 RNN 还是 MLP,从任何地方获得的基准测试结果都需要谨慎看待,因为结果可能因数据集不同而大相径庭。然而,图 4.8 显示了在 COCO 数据集上使用 GRU、LSTM 和一个考虑生成文本自然性度量的基准版本——生成的文本自然性越高越好。

图 4.8 – 在 COCO 数据集上进行图像标题任务的 RNN 基准测试
图中显示,LSTM-WM 在不同评估分数上的单次实验中优于其他方法。再次提醒,尽管如此,COCO 数据集并不代表序列数据或时间序列数据。
在你自己的数据集上试试看,确保了解!到此为止,我们已经讲解了 RNN 的重要概念,从基础到高级。接下来,让我们总结一下本章内容。
总结
循环神经网络是一种神经网络,明确在其结构中包括了序列数据的归纳偏置。
存在几种不同类型的 RNN,但它们的整体结构保持相同的高层次概念。主要是它们提供了不同的方式来决定从哪些数据中学习、记住哪些数据以及在记忆阶段从记忆中忘记哪些数据。
然而,请注意,最近出现了一种叫做 transformers 的架构,它将在第六章《理解神经网络 Transformer》一章中介绍,该架构证明了在处理序列数据时,递归并不是实现良好性能的必要条件。
到此为止,我们已经完成了对 RNN 的介绍,并将在下一章简要探讨自编码器的世界。
第五章:理解自编码器
自编码器是一种主要用于实现表示学习的模型类型。表示学习是一种深度学习任务,专注于生成紧凑且具代表性的特征来表示任何单个数据样本,无论是图像、文本、音频、视频还是多模态数据。经过某种形式的表示学习后,模型将能够将输入映射到更可表示的特征,这些特征可用于区分自身与其他样本输入。所获得的表示将存在于潜在空间中,不同的输入样本将共同存在。这些表示也称为嵌入。自编码器的应用将与表示学习应用紧密联系,一些应用包括为其他后续监督学习目标生成预测特征,比较和对比野外样本,以及执行有效的样本识别。
注意,自编码器并不是执行表示学习的唯一方式。表示学习的主题将在第八章,探索监督深度学习,以及第九章,探索无监督深度学习中进一步讨论。
现在,我们知道自编码器学习生成独特的表示或嵌入。但架构是什么样的?让我们先了解标准架构,然后再发现一些更有用的进展。
在本章中,将涵盖以下主题:
-
解码标准自编码器
-
探索自编码器变体
-
构建 CNN 自编码器
技术要求
本章包括一些在Python编程语言中的实际实现。要完成它,您需要安装以下库的计算机:
-
pandas -
Matplotlib -
Seaborn -
scikit-learn -
NumPy -
Keras -
PyTorch
代码文件可以在 GitHub 上找到:github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_5.
解码标准自编码器
自编码器更像是一个概念,而不是一个实际的神经网络架构。这是因为它们可以基于不同的基础神经网络层。处理图像时,您构建 CNN 自编码器;处理文本时,您可能希望构建 RNN 自编码器;处理包含图像、文本、音频、数字和分类数据的多模态数据集时,则使用不同层次的组合作为基础。自编码器主要基于三个组件,称为编码器、瓶颈层和解码器。这在图 5**.1中有所说明。

图 5.1 – 自编码器概念
标准自编码器的编码器通常接收高维数据,并将其压缩为比原始数据维度更小的任意尺度,这样就会生成所谓的瓶颈表示,瓶颈表示紧密联系着瓶颈部分,意味着它具有一个紧凑的表示,并不包含任何无用的信息。瓶颈部分随后会传递给解码器,解码器使用与编码器所用尺度完全相反的方式来扩展维度,最终生成与输入数据相同维度的输出数据。
注意
编码器和解码器结构并非仅限于自编码器,也被广泛应用于其他架构中,例如变换器(transformers)。
区别在于瓶颈部分,它承载着代表性特征。瓶颈通常被压缩为较小的维度,但有时也可以增大瓶颈的尺寸,以容纳更多具有预测能力的代表性特征。
自编码器通常是用来重建输入数据的。自编码器模型的训练过程涉及比较生成的输出数据与输入数据之间的距离。在优化过程中,模型能够生成接近输入数据的输出,当模型能够完全重建原始输入数据时,可以认为瓶颈部分比原始输入数据本身具有更加紧凑和概括性的表示。该紧凑的表示随后可以用来实现其他任务,例如样本识别,或者甚至可以通过存储较小的瓶颈特征来节省空间,而不是存储原始的庞大输入数据。编码器和解码器并不局限于单层结构,可以定义为多层结构。然而,标准自编码器只有一个单一的瓶颈特征,这也被称为代码,或潜在特征。
现在,让我们来探索自编码器的不同变种。
探索自编码器的变种
对于表格数据,网络结构可以非常简单。它仅使用一个包含多个全连接层的多层感知器(MLP),这些层逐渐减少编码器的特征数量,并使用多个全连接层逐渐增加解码器的输出数据,使其与输入数据的维度和大小相同。
对于时间序列或顺序数据,可以使用基于 RNN 的自编码器。关于基于 RNN 的自编码器,最常被引用的研究项目之一是使用基于 LSTM 的编码器和解码器的版本。该研究论文名为 Sequence to Sequence Learning with Neural Networks,作者为 Ilya Sutskever、Oriol Vinyals 和 Quoc V. Le(arxiv.org/abs/1409.3215)。该研究方法不是堆叠编码器 LSTM 和解码器 LSTM,而是将每个 LSTM 单元的隐藏状态输出序列垂直使用,解码器层按顺序继续编码器 LSTM 的顺序流,并以反向顺序输出重建的输入。还使用了额外的解码器 LSTM 层来并行优化,预测未来的序列。这个结构如 图 5.2 所示。需要注意的是,这种结构也可以通过使用原始的展平图像像素作为输入,适配到视频图像模态中。该架构也被称为 seq2seq。

图 5.2 – 基于 LSTM 的自编码器结构
编码器 LSTM 的隐藏状态输出可以被认为是 LSTM 自编码器的潜在特征。Transformers 是一种新型架构,也能处理顺序数据,后面将在本章介绍。它有一些变体也可以被视为一种自编码器——一些 transformers 可以是自编码器,但并非所有 transformers 都是自编码器。
对于图像数据,通过使用卷积层,我们可以通过多层卷积和池化层逐渐缩小特征,直到应用全局池化层并将数据转化为 1 维特征。这代表了自编码器的编码器生成瓶颈特征。这一工作流与我们在前一节中讨论的 CNN 相同。然而,对于解码器来说,为了将 1 维池化特征重新放大为 2 维的类似图像数据,需要一种特殊形式的卷积,称为 反卷积。
本节提到的变体都是关于使用标准的自编码器结构,但通过不同的神经网络类型来实现它们。还有两种额外的自编码器变体,其中一种变体基于数据输入的操作,另一种变体则是通过实际修改自编码器结构来实现数据生成目标。
对于用于数据处理的变体,其思想是在训练过程中向输入数据添加噪声,并保持原始输入数据不受噪声影响,作为目标用于预测性地重建数据。该自编码器的这种变体被称为去噪自编码器,因为其主要目标是去噪数据。由于目标已经从压缩数据转变为去噪数据,因此瓶颈特征不再限制于小尺寸。后续利用的特征不再仅仅局限于单一瓶颈特征,而是可以是网络中多个中间层的特征,或者简单地是去噪后的重建输出。该方法利用了神经网络执行自动特征工程的固有能力。去噪自编码器最著名的应用是在一个由葡萄牙保险公司 Porto Seguro 主办的基于表格数据的 Kaggle 竞赛中获得第一名的解决方案,在这个竞赛中,来自多个中间层的特征被输入到一个单独的多层感知机(MLP)中,以预测驾驶员未来是否会提出保险索赔(www.kaggle.com/competitions/porto-seguro-safe-driver-prediction/discussion/44629)。
对于使用自编码器结构修改的变体,其思想是生成两个瓶颈特征向量,分别表示标准差值和均值列表,以便根据均值和标准差值对不同的瓶颈特征值进行采样。这些采样的瓶颈特征值可以传递到解码器中,生成新的随机数据输出。该自编码器的变体被称为变分自编码器。
现在我们已经对自编码器的变体有了一个较好的概述,接下来让我们深入研究 CNN 自编码器,并使用深度学习库构建一个 CNN 自编码器。
构建 CNN 自编码器
让我们先来了解一下什么是转置卷积。图 5.3 展示了一个在 2x2 大小输入上使用 2x2 大小卷积滤波器,步幅为 1 的转置卷积操作示例。

图 5.3 – 转置卷积滤波操作
在图 5.3中,注意每个 2x2 的输入数据都标有从1到4的数字。这些数字用于映射输出结果,输出结果呈现为 3x3 的形式。卷积核会以滑动窗口的方式将每个权重分别应用到输入数据中的每一个值,四次卷积操作的输出结果会在图的下方呈现。操作完成后,每个输出结果将逐元素相加,形成最终的输出并加上偏置。这个示例过程展示了如何将一个 2x2 的输入扩展到 3x3 的输出,而无需完全依赖填充。
让我们在下面实现一个卷积自编码器模型,并在Fashion MNIST图像数据集上进行训练。这个数据集包含了鞋子、包包、衣服等时尚商品的图像:
-
让我们开始导入必要的库:
import torch.nn as nn import torchvision from PIL import Image -
接下来,我们将定义整个卷积自编码器的结构:
class ConvAutoencoder(nn.Module): def __init__(self): super(ConvAutoencoder, self).__init__() self.encoder = None self.decoder = None def forward(self, x): bottleneck_feature = self.encoder(x) reconstructed_x = self.decoder( bottleneck_feature ) return reconstructed_x这里展示的代码是一个基于
PyTorch的卷积自编码器结构,包含编码器和解码器的变量占位符。编码器负责接收一张图片并逐步减少其维度,直到只剩下一个具有小表示足迹的单维度——瓶颈特征。解码器接着会接收该瓶颈特征,并生成与原始输入图像大小相同的特征图。编码器和解码器将在接下来的两步中定义。 -
编码器将被设计为接收一个 28x28 的灰度图像(单通道)。这个图像尺寸是 Fashion MNIST 数据集的默认大小。以下逻辑展示了定义编码器的代码,替换掉步骤 2中定义的占位符:
self.encoder = nn.Sequential( nn.Conv2d(1, 16, 4), nn.ReLU(), nn.MaxPool2d(2, 2), nn.Conv2d(16, 4, 4), nn.ReLU(), nn.AvgPool2d(9), )定义的编码器有两个卷积层,每个卷积层后面跟着非线性激活函数
ReLU和一个池化层。卷积层的滤波器大小分别为16和4。第二个池化层是一个全局平均池化层,用于将 4x9x9 的特征图减少到 4x1x1,每个通道只有一个值来表示自己。这意味着编码器将压缩原始 28x28 图像的维度,原图的 784 个像素仅保留四个特征,压缩率高达 99.4%! -
解码器将接收每张图像的这四个特征,并再次生成 28x28 的输出特征图,重建原始的图像大小。整个模型没有应用填充。解码器将被如下定义,替换掉在步骤 2中定义的占位符解码器:
self.decoder = nn.Sequential( nn.ConvTranspose2d(4, 16, 5, stride=2), nn.ReLU(), nn.ConvTranspose2d(16, 4, 5, stride=2), nn.ReLU(), nn.ConvTranspose2d(4, 1, 4, stride=2), nn.Sigmoid(), )这里使用了三层卷积转置层。每个卷积层后面都有一个非线性激活层,前两层使用了
ReLU(作为标准的非线性激活函数),最后一层使用了sigmoid。使用sigmoid是因为fashion MNIST数据集已经被归一化,使得其值介于0和1之间。这里定义的卷积转置层采用了与编码器类似的滤波器配置,从16个滤波器到4个,最终使用1个滤波器生成单通道灰度图像。 -
现在我们已经定义了卷积自编码器,接下来让我们从
torchvision库加载时尚 MNIST 数据集。本教程将使用Catalyst库来简化训练过程,因此我们将从torchvision中获取fashion MNIST数据集加载器和喂入器类,并对其进行修改,以便在Catalyst库中使用:class FashionMNISTImageTarget( torchvision.datasets.FashionMNIST ): def __getitem__(self, index): img = self.data[index] img = Image.fromarray( img.numpy(), mode="L" ) if self.transform is not None: img = self.transform(img) return img, img来自
torchvision的类已经具备下载和加载fashion MNIST数据集的必要逻辑。然而,数据喂入方法getitem的格式不符合图像生成的预期格式,因此需要进行修改,以使本实验能够正常运行。 -
请注意,
Pillow库用于在第 5 步中加载图像。这是为了方便我们使用torchvision中的工具执行不同的转换步骤,例如图像增强。然而,在本实验中,我们将直接将pillow图像转换为Pytorch张量,使用如下的转换逻辑:def transform_image(image): return torchvision.transforms.ToTensor()(image) -
现在,让我们加载
fashion MNIST的训练和验证数据集:train_fashion_mnist_data = FashionMNISTImageTarget( 'fashion_mnist/', download=True, train=True, transform=transform_image, ) valid_fashion_mnist_data = FashionMNISTImageTarget( 'fashion_mnist/', download=True, train=False, transform=transform_image, ) loaders = { "train": DataLoader( train_fashion_mnist_data, batch_size=32, shuffle=True ), "valid": DataLoader( valid_fashion_mnist_data, batch_size=32 ), }上面的代码会将数据集下载到
fashion_mnist文件夹中(如果该文件夹不存在)。此外,loaders变量也在这里定义,以便可以被Catalyst库使用。 -
由于优化目标是减少重建像素值与目标像素值之间的差异,因此我们将在这里使用均方误差作为重建损失:
criterion = nn.MSELoss()需要注意的是,尽管重建损失是无监督表示学习中的常见目标,但根据具体的算法或方法,可能会使用其他的指标或目标。例如,在
Catalyst中,我们可以用来训练我们的模型:runner = dl.SupervisedRunner( input_key="features", output_key="scores", target_key="targets", loss_key="loss" ) -
接下来,我们将定义一个通用的函数,使得通过代码执行多个训练和验证实验变得更加简单:
def train_and_evaluate_mlp( trial_number, net, epochs, load_on_stage_start=False, best_or_last='last', verbose=False ): model = net optimizer = optim.Adam( model.parameters(), lr=0.02 ) checkpoint_logdir = "logs/trial_{}_autoencoder".format( trial_number) runner.train( model=model, criterion=criterion, optimizer=optimizer, loaders=loaders, num_epochs=epochs, callbacks=[ dl.CheckpointCallback( logdir=checkpoint_logdir, loader_key="valid", metric_key="loss", load_on_stage_end='best', ) ], logdir="./logs", valid_loader="valid", valid_metric="loss", minimize_valid_metric=True, verbose=verbose, ) with open( os.path.join(checkpoint_logdir, '_metrics.json'), 'r' ) as f: metrics = json.load(f) if best_or_last == 'last': valid_loss = metrics['last']['_score_'] else: valid_loss = metrics['best']['valid']['loss'] return valid_loss这些只是基本的训练和评估模板代码,没有应用任何技巧。我们将在第八章中深入探讨训练监督模型的技巧,探索监督学习 深度学习。
-
现在,我们准备通过以下逻辑来训练和评估 CNN 自编码器模型:
cnn_autoencoder = ConvAutoencoder() best_valid_loss = train_and_evaluate_mlp( 0, cnn_autoencoder, 20, load_on_stage_start=False, best_or_last='last', verbose=True )基于验证损失,表现最好的
cnn_autoencoder权重将在训练完成 20 个 epoch 后自动加载。 -
使用提供的训练代码训练前述模型后,在 Fashion MNIST 数据集(图像尺寸为 1x28x28)上,您应该会得到类似于 图 5.4 中所示的示例输入/输出对,通过以下代码:
input_image = valid_fashion_mnist_data[0][0].numpy() predicted_image = cnn_autoencoder( torch.unsqueeze(valid_fashion_mnist_data[0][0], 0) ) predicted_image = predicted_image.detach().numpy( ).squeeze(0).squeeze(0) f, axarr = plt.subplots(2,1, figsize=(5, 5)) axarr[0].imshow(predicted_image, cmap='gray') axarr[1].imshow(input_image.squeeze(0), cmap='gray')

图 5.4 – Autoencoder 在 Fashion MNIST 数据集上的示例结果;底部表示原始图像,顶部表示重建图像
从结果来看,可以清楚地看出瓶颈特征能够相对完整地重建整个图像,它们可以作为更具代表性和紧凑的特征,代替原始输入数据使用。将瓶颈特征的表示值从 4 增加到大约 10 个特征,也应该能提高重建图像的质量。欢迎尝试并实验不同的参数!
总结
Autoencoder 被认为是实现跨数据模态表示学习的基本方法。可以将其架构视为一个框架,您可以在其中嵌入多种神经网络组件,从而允许您处理不同模态的数据或利用更先进的神经网络组件。
但是,请注意,它们并不是唯一的学习表示特征的方法。Autoencoder 还有许多应用,主要围绕不同的训练目标,使用相同的架构。在本章中简要介绍的两个变体是去噪自编码器和变分自编码器,它们将在 第九章《探索无监督深度学习》中得到详细介绍。现在,让我们再次转变思路,探索 Transformer 模型系列!
第六章:理解神经网络 Transformer
不要与同名的电气设备混淆,神经网络中的 Transformer 是多才多艺的 NN 变体。Transformer 能够处理并捕捉来自任何模态的数据模式,包括顺序数据如文本数据和时间序列数据、图像数据、音频数据和视频数据。
Transformer 架构于 2017 年提出,目的是取代基于 RNN 的序列到序列架构,主要聚焦于机器翻译任务,即将文本数据从一种语言转换为另一种语言。其表现优于基准的 RNN 模型,证明了我们不需要像 RNN 那样依赖数据的顺序性质(内在归纳偏置)。此后,Transformer 成为了神经网络架构家族的根基,并衍生出能够捕捉其他数据模态模式的模型变体,同时持续提升原始文本序列数据任务的性能。这表明,我们实际上不需要在模型中内置与数据模态相关的归纳偏置,而是可以让模型学习这些内在的结构和模式。你有顺序数据,比如视频、文本或音频吗?让神经网络学习它的顺序特性。你有图像数据吗?让神经网络学习像素之间的空间和深度关系。你大概明白了。
在我们深入探讨 Transformer 之前,先休息一下,看看本章将涵盖的主题:
-
探索神经网络中的 Transformer
-
全面解码原始 Transformer 架构
-
仅使用编码器来发现 Transformer 的改进
-
仅使用解码器来发现 Transformer 的改进
探索神经网络中的 Transformer
图 6.1 提供了 Transformer 所带来的影响概述,感谢各种 Transformer 模型变体的涌现。

图 6.1 – Transformer 的不同模态和模型分支
变压器本身的架构并没有内在的归纳偏差。归纳偏差是指学习算法对数据做出的预假设。此偏差可以在模型架构或学习过程中构建,并有助于引导模型学习数据中的特定模式或结构。传统模型,如 RNN,通过其设计引入归纳偏差,例如假设数据具有顺序结构,且元素的顺序很重要。另一个例子是 CNN 模型,这些模型专门为处理网格状数据(如图像)设计,通过使用卷积层和池化层来引入归纳偏差,表现为局部连接性和位移不变性。在这种情况下,模型架构本身对能够学习的模式施加了某些约束。
变压器的设计理念是,我们应该允许模型根据提供的所有输入数据决定如何以及在哪里聚焦。这一过程通过多个机制的汇聚来技术性地执行,每个机制以不同的方式决定关注的焦点。因此,变压器完全依赖输入数据提供的信息来确定数据是否具有任何形式的归纳偏差(如果有的话)。这些聚焦组件正式被称为注意力层(attention layers)。在变压器之上进行的改进,形成了你在图 6.1中看到的分支,它们在结构上并没有偏离变压器的基础结构。通常,添加不同模态的改进是通过数据设置变体来完成的,以使输入数据结构适应变压器的结构,并根据目标任务应用的变化调整不同的预测输出细节。
目前,变压器(transformers)是具有最大学习和识别能力的架构家族,能够识别现实世界中存在的高度复杂的模式。除此之外,从 2022 年中到 2023 年近一年的时间里,变压器展示了其信息处理能力仅受硬件资源能力的限制。目前,最大的变压器模型之一是 Google 提供的PaLM,它比基础变压器扩展了许多倍,具有天文数字般的5400 亿参数。此外,坊间传闻,OpenAI 的 GPT-4 多模态文本生成模型(目前作为服务提供而非开源模型)包含多个模型或单个模型,其参数总和超过一万亿。这些庞大的模型通常需要数月的训练才能实现最佳性能,并且需要高性能的、最先进的 GPU。
你可能会好奇,为什么我们要训练这样一个庞大的模型,并且会怀疑它所提供的价值是否值得。让我们评估一下其中一个 Transformer 模型——GPT-3,这是由 OpenAI 开发的。GPT-3 是一种语言模型,它接收输入文本并基于它认为最合适、最有用的响应生成文本预测。这个模型涉及许多 NLP 领域的任务,这些任务传统上需要为每个任务使用不同的模型来完成,这使得它成为一个任务无关模型。它能够完成的任务包括机器翻译、阅读理解、推理、算术处理等,并且在展示广泛的语言理解能力方面表现出色,根据查询输入的文本格式提供不同的结果。例如,终端用户的应用包括生成能实现所描述目标的任何指定语言的代码,撰写具有指定主题的小说,获取关于任何公众人物的任何请求信息,总结客户反馈,通常或聚焦于特定话题,如“客户的烦恼是什么”,以及为虚拟世界中的角色对话增加现实感。迄今为止,GPT-3 已被应用于 300 多个领域和行业,虽然这个数字看似不大,但它为更多的创新和神经网络的广泛应用铺平了道路。
Transformer 经过多年的渐进式改进和严谨研究,虽然不断演化,但并未偏离太多原始架构。这意味着,理解原始架构对于理解所有最新和最强大的改进版 Transformer(如 GPT-3)至关重要。基于此,我们来深入了解 2017 年的原始 Transformer 架构,并讨论过去五年中研究带来的哪些组件发生了变化或适应,催生了新的模型架构。
全面解读原始的 Transformer 架构
在我们深入探讨模型结构之前,先来谈谈 Transformer 的基本目的。
正如我们在上一章中提到的,Transformer 也是一种架构族,利用了编码器和解码器的概念。编码器将数据编码成所谓的代码,解码器则将代码解码成类似于原始未处理数据的格式。最初的 Transformer 同时使用了编码器和解码器的概念来构建整个架构,并展示了其在文本生成中的应用。随后的适应和改进则要么仅使用编码器,要么仅使用解码器来完成不同的任务。然而,在 Transformer 中,编码器的目标并不是压缩数据以实现数据的更小、更紧凑的表示,而是主要作为特征提取器。此外,Transformer 中解码器的目标并不是重新生成相同的输入。
注意
可以使用 Transformer 组件构建一个实际的自动编码器结构,而不是使用 CNN 或 RNN 组件,但本书中将不涉及这一部分内容。
原始的 Transformer 将编码器和解码器中的数据/权重维度固定为统一的单一大小,从而能够进行残差连接,使用的维度为 512。
Transformer 还利用逻辑块结构来定义其创新之处,这使得你可以轻松扩展其规模。Transformer 的核心操作是识别输入数据样本中每个数据单元应关注整个输入数据的哪一部分。这种聚焦机制是通过一种叫做注意力层的神经网络层来实现的。关于注意力机制的许多变种在本书中将不会涉及。
Transformer 使用了最简单的注意力变种,该变种使用 softmax 激活函数来实现,softmax 强制所有值的和为 1.0,通常每层会有一个最强的焦点。可以将其视为类似于 RNN 中的门控机制,具体细节可以参考 第四章,理解循环神经网络,以获得关于 RNN 的完整背景和它的工作原理。Transformer 使用多个注意力层,在这些层中,每个输入将以多种方式进行聚焦,而为了简化引用和扩展,这些层被统称为 多头 注意力 层。
变压器的输入和输出是以标记(tokens)的形式存在的。标记是指代可以传递进出变压器的输入数据的单个单位。尽管变压器的结构并没有明确设计以适应这些假设,这些标记可以是顺序的或非顺序的,分组的或非分组的。通过自变压器提出以来的研究和进展,文本的标记可以是词语或子词,图像的标记可以是图像块,音频的标记可以是音频数据的顺序时间窗口,例如,一秒钟的时间窗口,视频的标记可以是单个图像帧或一组图像帧。由于模型本身没有对任何数据模态类型的内在归纳偏见,因此数据的位置信息或顺序信息是通过标记显式地编码到数据中的。这些可以从增量的离散整数位置(1, 2, 3 …)到介于离散整数值之间的连续浮动位置(1.2, 1.4556, 2.42325 …),从绝对位置到相对位置,最后,每个标记或嵌入的单一值位置可以是可学习的。原始的变压器使用了一种映射函数,将绝对整数位置映射到考虑标记数据维度和预定统一数据维度大小的相对浮动位置,但现今最先进的模型通常采用可学习的嵌入。嵌入是一个查找表,用于将任何离散的类别数据编码为原始类别的更高维度和更复杂的表示,可以准确地区分不同的类别。请注意,实际的输入数据标记本身通常也会根据输入数据(图像帧或块通常不使用嵌入,因为它们已经具有较高的维度,但文本标记是类别性的,因此会使用嵌入)应用嵌入。
图 6.2展示了架构的高级概览:

图 6.2 – 通过输入和输出的可视化查看变压器,其中包括用于将英语句子翻译成普通话对应句子的编码器和解码器任务
编码器和解码器结构有些相似,唯一的区别是解码器多了一个中间的softmax激活函数。
注
解码器还会接收除编码器输出之外的其他数据。
为了在输出数量没有约束的情况下实现数据生成,转换器被设计成一个自回归模型。自回归模型使用第一次预测的结果作为第二次预测的输入,并随后用作接下来的预测输入。下图展示了这个预测过程的一个示例,使用基于文本的数据,旨在实现从英语到普通话的机器翻译。

图 6.3 – 转换器的自回归工作流
转换器的每次前向传递都会预测一个单一的输出。在转换器输出时,预测的句末标记会被加入,表示预测完成。然而,在训练过程中,不需要执行这个自回归循环;相反,会在随机位置生成掩码,屏蔽所有目标标记之后的未来标记,以防止模型直接复制单个目标标记并获取未来标记的信息。简单来说,掩码机制在预测或推理阶段不会被使用。
除了实际的注意力机制外,多头注意力层还有一些其他关键操作。图 6.4 显示了解开后的多头注意力层,以及之前提到的在缩放点积注意力层中的注意力机制。

图 6.4 – 使用缩放点积注意力结构的多头注意力层
将多个注意力层视为多个不同的聚焦模式在人类对相同数据的贡献。softmax来自聚焦在不重要的区域。注意力机制旨在找出每个标记与整个输入标记集合的相关程度,换句话说,就是找出哪些标记与哪些标记进行了交互,并弄清楚它们之间的依赖关系。回想一下,模型的维度必须在整个架构中保持固定大小,以确保值可以在不需要额外处理的情况下相加。由于多个注意力头的输出将被连接而不是相加,因此线性层的维度必须在各个头之间均匀分配。例如,使用 8 个头和 512 的数据/权重维度,每个头的查询、键和值的线性层维度将是 512/8=64 个神经元。图 6.5 显示了输出及其形状的一个简单示例,工作流中每个标记的固定数据/权重维度为 3,使用与图 6.4相同的输入文本数据。

图 6.5 – 在上下文“I love you”中,使用词语“I”的查询与输出维度为 3 的线性层连接之前的多头注意力部分操作
对于其他两个查询词“love”和“you”,也会执行相同的操作。单个头部的操作以一种方式关注输入数据,而其他头部则以不同的方式关注输入数据。在编码器-解码器层中,查询来自解码器层,而键和值来自编码器层。这使得解码器能够选择在给定的输入文本中集中注意力的位置,以生成下一个输出。图 6.6展示了一个很好的方式来思考多头注意力的输出,其中四个头的多头注意力关注文本的不同部分。

图 6.6 – 带有四个头的多头注意力示例输出
Transformer 中的正则化机制与其他先前的架构(如 CNN、RNN 和 MLP)中描述的目的一致,这部分内容在这里不再赘述。这总结了 Transformer 的所有组件。这个基础架构使我们能够比旧的架构(如序列到序列或自编码器)获得更好的数据生成结果。尽管原始架构主要关注文本数据的应用,但该概念已被改编并适用于处理其他类型的数据模态,如图像、视频和音频数据。回想一下,原始模型之后的改进和适应仅使用了编码器或仅使用了解码器。在接下来的两个主题中,我们将分别深入探讨这两个不同的概念。
揭示仅使用编码器的 Transformer 改进
我们将讨论的第一类基于变压器的架构进展是仅使用原始变压器的编码器部分并使用相同多头注意力层的变压器。采用仅编码器的变压器线通常是因为没有使用掩蔽的多头注意力层,因为不使用下一标记预测训练设置。在这类改进中,训练目标和设置会根据不同的数据模态有所变化,并且在相同数据模态下的序列改进也会略有不同。然而,有一个概念在不同数据模态中保持相对恒定,那就是使用半监督学习方法。在变压器的情况下,这意味着首先执行一种无监督学习方法,然后再执行直接的监督学习方法。无监督学习为变压器提供了一种基于对数据特性更广泛理解来初始化其状态的方式。这一过程也被称为预训练模型,这只是一种无监督学习的形式。无监督学习将在第八章中进行更为详细的讨论,探索无监督 深度学习。
一些改进是针对数据模态的特性进行的,主要包括文本特定的改进,并涉及优化任务类型的变化。其他改进则是通用的变压器架构改进,可以推广到不同的数据模态。我们将从专注于通用架构改进开始,然后再深入讨论专门为文本量身定制的改进,这些改进可能适用于其他数据模态,也可能不适用。首先,我们将介绍一种仅包含编码器结构的基础架构,称为 BERT。
softmax层。这个特定任务已被证明是作为预训练方法非常有效,它允许下游后续的监督任务获得更好的表现。由于标记在原始序列中的不同位置被掩蔽,因此该架构被称为双向的,因为除了过去的标记外,未来的标记也可以被注意并预测过去的标记。例如,15%的标记被掩蔽,其中 10%被随机替换为随机标记,充当噪声,这使得模型类似于去噪自编码器,并使其对噪声具有鲁棒性。标准的自回归结构通常是单向的,无法利用未来的标记。BERT 还引入了一种方法,通过单个输入表示编码多个句子。这是通过添加一个特殊的分隔符标记来实现的,该标记具有自己的学习嵌入,用于表示文本之间的分隔,以及一个额外的段落嵌入,标识该标记属于哪个句子。这还允许进行另一项任务——softmax。这三种嵌入(位置嵌入、段落嵌入和标记嵌入)在传递给 Transformer 之前被加在一起。图 6.7展示了这一架构。

图 6.7 – BERT 架构
该架构专注于表示额外输入的方式,并通过多任务学习增强模型对数据模态性质的理解,超越了之前在多个下游文本任务上的表现。
现在我们已经介绍了基础的仅编码器模型,准备探索针对仅编码器 Transformer 的不同类别的进展。此次改进将按进展类别进行探索,而非按模型分类,因为不同的模型结合了许多改进,比较实际的进展会变得困难。三种进展类型如下:
-
改进基于数据模态的预训练任务
-
在紧凑性、速度和效率方面的架构改进
-
核心/功能性架构改进
我们将跳过基于数据的改进细节,例如使用多语种数据构建多语种 BERT,或使用基于子词的标记来减少词汇量并增加词汇复用性的问题。
改进仅编码器的预训练任务和目标
回想一下,MLM 和 NSP 是基础 BERT 模型中使用的任务,其中 MLM 被提到是一个有助于理解文本语言的强大任务,但它可以很容易地适应来自其他数据模态的 token。相比之下,NSP 已被证明不稳定,并且并非在所有情况下都能切实帮助模型提升性能。一个改进方向是使用句子顺序预测(SOP)。与仅仅使用同一文档中正向连续句子的倒序作为负句相比,SOP 在提升下游监督任务性能方面展现了更高的一致性。从概念上讲,它学习句子之间的衔接性,而不是同时预测两句话是否来自同一主题。这一方法是在 2020 年的ALBERT模型中引入的,该模型主要关注于 Transformer 的效率。
另一个值得注意的改进是替代 token 检测(RTD)。RTD 来自ELECTRA模型,它预测一个 token 是否被随机 token 替代,并且要求学习所有 token 位置的信息,这与 MLM 只从掩码 token 位置进行学习的过程不同。这是对 MLM 目标的改进。
接下来,我们将深入探讨在架构紧凑性、速度和效率方面的改进。
提升仅编码器 Transformer 的架构紧凑性和效率
Transformer 已经证明它们具备极大的结构能力,能够接收随模型增长的信息。一些关于 Transformer 的研究集中在将 Transformer 模型扩展到数百亿参数的规模,无论是在仅解码器还是仅编码器模型中。使用这些庞大模型所取得的成果令人惊叹,但由于需要使用最先进的 GPU 和机器,这些机器并不是大多数人都能轻松获得或负担得起的,因此这些模型不适合实际使用。这时,架构紧凑性和效率的改进可以帮助平衡这一差距。由于我们大多数人受限于可用的硬件资源,如果模型能在保持相同性能的前提下,在架构上有效缩小尺寸,这将使得在将模型扩展到硬件资源极限时,能够提供更高的性能。
该领域的另一项显著进展再次来自 ALBERT 模型,这是一种轻量级 BERT 模型。其主要改进在于以卷积层被分解的方式分解了嵌入层,从而实现深度卷积。使用较小的嵌入维度,并通过一个全连接层将较小维度的嵌入映射到所需的相同维度。由于嵌入是必需的权重,并且对参数的数量有贡献,分解嵌入层使得变换器模型的参数数量更少,从而使得 BERT 在较少参数的情况下具有更好的性能。需要注意的是,一些改进增加了模型并行性并使用了巧妙的内存管理,但这些内容在此不作详细讨论。不过,由于有一些方法能在推理和训练阶段加速模型,因此这些内容将在第十五章《在生产中部署深度学习模型》中讨论,该章专门讲解部署。
改进仅编码器变换器的核心功能架构
该领域最显著的进展来自于 2021 年推出的解码增强 BERT 与解耦注意力(DeBERTa)模型,它被认为是当前架构的 SOTA。其主要思路是使用两个独立的位置编码嵌入,分别是相对位置和绝对位置。然而,这些位置嵌入并不是与输入令牌嵌入一起相加的,而是作为独立的查询和键被输入到变换器的层中,并拥有自己的全连接层。这使得可以获得三种显式的注意力图,具体包括内容到位置的注意力、内容到内容的注意力和位置到内容的注意力。然后,这些不同的注意力图会被加总在一起。相对位置特别用于每个中间多头注意力层的内部缩放点积注意力层,作为键或查询,取决于它是被内容关注还是关注内容。
这允许在每一层注意力机制中显式地考虑原始相对位置的关系,而不是在经过多层后可能被遗忘。我们本质上是在为相对位置数据应用一种跳跃连接的形式。相对编码使得模型能够通过概念上允许在每个相对位置上重用模式标识符,从而学习到更具泛化性的表示。这种解决方案类似于如何在单幅图像的不同分区使用相同的卷积滤波器,而不是在整个图像上使用一个全连接层,这样做无法跨整个输入空间重用模式标识符。至于绝对位置的使用,它仅仅是由于文本数据的特性需要绝对位置,以便区分不同事物的绝对重要性,例如警察比大多数人拥有更多的权力,卡车比摩托车大。然而,绝对位置仅在最后几层中添加一次,使用一种叫做增强掩码解码器(EMD)的机制。EMD 将绝对位置嵌入作为缩放点积注意力的查询组件。没有明确的理由说明为什么只在最后几层应用,实际上它可能可以扩展到每一层。
分桶
使用分桶机制将多个位置组合成一个数字,以减少嵌入的数量,从而减少参数数量。可以将其视为分组位置信息,再次增加了模型的紧凑性。
揭示仅编码器变换器如何适应其他数据模态
变换器能够处理其他数据模态。首先,输入数据只需要以序列格式结构化。之后,由于预训练是变换器的核心,需要为仅编码器变换器制定合适的任务目标,以实现一个能够捕捉特定数据模态理解的预训练状态。尽管使用文本引入变换器时,重点放在无监督预训练方法上,但它实际上可以是监督、无监督或半监督的,关键在于将模型的权重设置为一个状态,以便后续的监督下游任务能够加以利用。
对于图像,Vision Transformer(ViT)于 2021 年推出,形成了基于图像的变压器的基础,并证明了与卷积基础模型相比,变压器在图像任务中能实现竞争性的性能。变压器处理图像时最棘手的问题之一是图像的体积庞大,处理整个大输入空间会迅速膨胀变压器模型的大小。ViT 通过将图像拆分为系统的补丁来解决这个问题,每个补丁随后会输入到一个单一的全连接层中,从而大幅度减少每个图像补丁的维度。维度已减少的补丁数据将作为变压器的令牌嵌入。补丁转换是将图像输入以变压器可以处理的格式表示的关键过程。此外,这种基于补丁的机制引入了一些形式的图像领域归纳偏差,但架构的其余部分几乎不受图像数据归纳偏差的影响。由于变压器几乎总是与预训练方法结合使用以提高性能,ViT 采用了类似于 CNN 模型系列的预训练方式,使用监督分类预训练。
对于音频,wav2vec 2.0于 2020 年创建,采用了与其前身wav2vec相似的概念,但不同的是它在架构中采用了仅解码器的变压器。简要概述是,wav2vec 2.0 首先使用一维卷积将特定时间窗口长度的音频编码为一个较低维度的空间,作为特征向量。每个窗口的编码特征向量随后会被输入到解码器变压器中并作为令牌。这里应用了无监督预训练方法,类似于 BERT 中的 MLM,其中输入的编码特征向量的某些部分被屏蔽,任务是预测被屏蔽的编码特征向量。预训练后,模型将在下游任务中进行微调,通常在变压器输出之后接一个线性层,用于语音识别等任务。wav2vec 2.0 在下游语音识别数据集上实现了新的 SOTA,并证明了音频与变压器的兼容性极好。
VideoBERT,由 Google 在 2019 年创建,不仅展示了如何为 transformer 模型建模视频数据的方法,还展示了 transformer 模型以多模态方式同时学习视频和文本的联合表示的能力。需要注意的是,视频本身就是一种多模态数据,因为视频数据包含多个图像帧以及音频数据组件。在 VideoBERT 中,使用带有明确起始和结束时间戳注释的烹饪视频句对数据集来对模型进行预训练。该模型通过将一预设数量的帧通过预训练的 3D 卷积模型编码成特征向量并将其视为 token 来处理图像帧序列。这些编码后的视频 token 随后与文本数据配对,并使用类似于 MLM 的目标来预测视频和文本 token 的掩码 token。该模型可以作为视频和文本相关数据的特征提取器,或者通过在 transformer 模型输出上增加一个线性层进行微调。
不同数据模态的前述总结旨在提醒我们,transformer 不仅可以应用于文本数据,还能应用于更多的数据模态,而不是作为这些模态当前技术状态的介绍。这里的一个关键点是,不同的数据模态需要被排列成具有降维的 token 格式序列,并且需要为仅编码器的 transformers 制定合适的任务目标,以便其达到能够捕捉数据模态主要特征的预训练状态。此外,BERT 的预训练方法可以轻松适配到其他数据模态。因此,与其称之为“masked language modeling”,不如称之为“masked token modeling”。接下来,让我们揭示仅使用解码器的 transformer 改进。
使用仅解码器的 transformer 改进
回想一下,transformer 的解码器模块专注于自回归结构。对于仅解码器的 transformer 模型,预测 token 的自回归任务保持不变。随着编码器的移除,架构必须调整其输入,以接受多个句子,类似于 BERT 的做法。开始、结束和分隔符 token 被用来顺序编码输入数据。在预测过程中,依然会进行掩码操作,以防止模型依赖当前 token 来预测未来的输入数据 token,这与原始 transformer 及位置嵌入相似。
深入了解 GPT 模型家族
所有这些架构概念都是由 GPT 模型在 2018 年提出的,GPT 是生成预训练(Generative Pre-training)的缩写。顾名思义,GPT 也采用了无监督预训练作为初始阶段,随后进入监督微调阶段以应对下游任务。GPT 主要关注文本数据,但相同的概念也可以应用于其他数据模态。GPT 使用基础的语言建模任务进行预训练,通过给定前面的标记作为上下文,在一个固定的窗口中预测下一个标记,从而限制输入的上下文标记数量。在预训练权重后,模型会根据任何监督任务类型的目标进行微调。GPT-1 在语言建模任务目标的辅助作用下,与主要的监督任务一起使用,试图提升下游任务的表现,并且证明它只在较大的数据集上能提高表现。此外,模型首次展示了将语言建模作为预训练任务应用于其他任务的泛化能力,即使不进行微调,仍能取得良好的成绩。这种行为被称为零-shot,将在第九章,《探索无监督深度学习》中详细讨论。该模型在 7,000 本未公开的书籍上进行了预训练,具有 1.17 亿个参数和 12 个重复的解码器块。
需要明确的是,GPT 模型能够在不进行微调的情况下执行任务(少样本或零样本学习)并不意味着微调在特定领域任务中的好处可以被忽略。尽管 GPT 系列模型在各种任务中展示了令人印象深刻的能力,但对于特定应用,微调仍然可能具有优势,从而提升性能并更好地适应给定任务的独特需求。
进入下一个进展,GPT-2 在架构上没有进行实质性的变化,其主要思想是强调零-shot 学习的能力,并通过任务条件化的概念,将 GPT-1 模型在层数、嵌入维度、上下文窗口大小和词汇量上扩大了 10 倍。任务条件化的概念是指你告诉模型应该生成什么样的输出,而不是仅仅依赖于训练任务类型所决定的固定、单一的已知预测输出。为了实现类似人类的真正灵活性,它们并没有添加新的架构来适应任务条件化的思想,而是决定直接将变换器的文本输入用作任务条件化的方法。例如,像“我爱你 =
,我爱深度学习=”这样的自由形式文本输入自然引导变换器执行从英语到普通话的翻译。也可以通过另一种方式来指定任务和上下文,例如“(翻译成普通话),我爱深度学习 =”。GPT-2 通过在各种文本领域(如新闻文章、小说、维基百科和博客)上进行语言建模目标的预训练,实现了这一点,基本上使用了比 GPT-1 训练数据集更大的数据集。这项工作展示了真正的 AI 泛化潜力,尽管目前只使用了文本和图像数据。对于图像数据,这是通过一个名为DALL-E的模型来实现的,该模型能够根据提供的输入文本生成图像。
最后,GPT-3 模型在 2020 年推出。它类似于之前的进展,主要变化是增加了模型各个组件的规模,并且通过不同的方式来规定任务条件,以适应不同类型的文本示例。我们这里不会深入讨论这一点。尽管 GPT-2 在零-shot 设置中表现良好,且不需要对下游任务进行微调,但只有通过微调,该模型才能在多个文本语言数据集上超越以前的基准。GPT-3 的庞大规模彻底消除了微调的需求,在没有任何微调的情况下超越了之前的基准。然而,该模型需要专门的机器进行训练,个人或小型组织难以直接使用。目前,它是 OpenAI API 服务的一部分。GPT-3 展示了在许多不同任务中的广泛适用性,无需任何微调,其中一些最引人注目的应用包括为任何编程语言生成代码、讲故事和制作非常逼真的聊天机器人。这三项改进推动了许多基于语言任务的数据集的性能限制,使其达到更高水平,并且作为机器学习普适性组件的一个例子,展示了 transformers 的巨大学习潜力,只受到硬件资源的限制。
接下来,我们将讨论一种略有不同的解码器模型,值得一提的是 XLNet 模型。
深入了解 XLNet 模型
如果你和你的朋友被给出提示“_ _ 是一座城市”,你们两个会分别预测填入空白的是“纽约”吗?这是编码器-仅 transformers 的 MLM 目标的一个缺陷,因为它们通过一次预测多个标记来学习。然而,标准的自回归模型由于其自回归的特性,只预测未来并且一次只预测一个标记,因此不会受到这个问题的影响。编码器-仅 transformers 具有双向序列支持,这有助于提高性能。XLNet 主要基于这样一个思想:使自回归模型以双向方式关注数据,就像编码器-仅模型一样,同时保持自回归模型预测单一输出的优势。
XLNet 通过在 MLM 预训练期间调节解码器单独的变换器来实现双赢。其思路是通过使用掩蔽机制,预训练模型在所有标记序列顺序的排列上进行训练。标准自回归模型在预训练时按“1-2-3-4”的顺序使用来预测第五个标记,但在 XLNet 中,可以将顺序改为“3-4-1-5”来预测第二个标记。XLNet 还引入了另一个额外的查询线性层隐藏状态,并添加了一个额外的单一固定可学习嵌入,将与每个标记嵌入一起穿过注意力层,作为与原始基于标记的内容流一起的额外流路径。这个额外路径被称为查询流。整个过程称为掩蔽的双流注意力层,如图 6.8所示。

图 6.8 – XLNet 工作流
内容基于流的前向传播机制对于序列排序中的任意位置具有能够关注其自身位置和之前位置的内容的能力,而基于查询的流对于序列排序中的任意位置仅具有关注其之前位置内容的能力。图 6.8 (a) 显示了在模型中实际位置 1 的内容流的注意力操作如何能够关注其他位置的所有标记,因为“3-2-4-1”排序方式将位置 1 排列为最后一个标记。图 6.8 (b) 显示了如何通过位置 1 的标记内容来更新额外的查询流路径数据(表示为g),并且g组件作为查询来获取下一个g作为注意力层的输出。如果模型的第四个位置需要更新,内容流中将仅关注第二和第三个位置的内容,因为根据排列后的序列顺序,第一个位置是未来的标记。基于此概念动态应用掩蔽机制,以防止每个位置的h和g在定义之前关注排列序列中的内容位置。注意,图 6.8 (c) 中的w组件表示共享于所有位置的单一权重嵌入,适用于查询流。在预训练过程中,查询流作为最终分类输出路径,适用于 MLM 目标,其中最终的h组件会被忽略。然而,在传递到微调阶段时,查询流组件和基于查询的线性层隐藏状态会被丢弃或完全忽略。
通过在预训练期间利用单词预测的语言建模目标,同时使数据的双向上下文得以实现的创新方法,提升了下游监督学习任务的性能。
讨论解码器单独的变换器模型的其他进展
自 GPT 问世以来,基础的解码器架构没有发生任何具有重大影响的变化。大多数工作集中在以下三个方面:
-
将 transformer 应用于不同的任务类型
-
将模型规模扩大到数千亿个参数
-
专注于工程解决方案,以使得一个巨大的模型可以在现有的硬件资源上进行训练
其中一个最著名的工作是利用解码器 transformer 处理其他任务类型的 DALL-E 模型,由 OpenAI 团队开发。在 DALL-E 的第一版和第二版中,transformers 被用来自回归地预测一个紧凑的图像嵌入,给定文本输入和一些其他特定于实现的输入。这些图像嵌入随后通过其他机制转换成实际的图像嵌入。
对于模型扩展,GPT-3 展示了随着模型容量的增加,性能也可以得到提升。首先提到的是Megatron,该模型通过工程策略使 transformer 架构更加高效,实现了并行化,并且能够使用 512 个 GPU 进行训练,使其比 GPT-2 大了 5.6 倍。GPT-3 超越了 Megatron,但另一个名为BLOOM的模型使用 Megatron 作为基础,将其扩展到 176B,匹配了 GPT-3 的 175B 模型规模。最值得注意的并不是它比 GPT-3 更大,而是 BLOOM 开创了一个新范式——开源,这意味着研究人员和机构的集体工作和贡献也可以在大规模上训练出高效且具有优异表现的模型。过去,这些庞大的模型一直是大型企业独占的,因为它们需要极其庞大的硬件资源。除此之外,另一个在 2022 年发布的模型PaLM将 GPT 的规模扩展到了 5400 亿个参数,超过了其他所有较小的 GPT 变体模型的性能。PaLM 2通过在多个任务上提供增强的性能,超越了其前身,包括英语和多语言理解、推理和代码生成。它通过优化计算扩展法则、利用多样的多语言数据集以及实施架构改进实现了这一目标。PaLM 2 在多语言能力、分类、问答和翻译任务中表现突出,展示了其广泛的适用性。该模型还结合了控制令牌来减少毒性,并为负责任的发展和部署提供了指导原则。
在为特定任务选择变换器模型时,采取平衡的方法至关重要,除了性能指标外,还需要考虑多个因素。模型的大小和资源需求在确定所选模型的可行性和适用性方面起着重要作用。尽管像 GPT-3 这样的大型模型展现了令人印象深刻的能力,但它们的资源需求可能并不适合所有情况,特别是对于计算资源有限的个人或小型组织。
在为特定任务选择变换器模型时,权衡模型的大小、性能和资源需求是至关重要的。在某些情况下,小型模型可能提供足够的性能,同时更高效且更环保。此外,尽管模型的规模不如一些最知名的模型(如 GPT-3),但通过微调通常可以提高模型在特定领域任务中的表现。最终,最佳选择取决于特定任务的独特需求和限制,以及可用于模型训练和部署的资源。
总结
变换器是多功能的神经网络(NN),能够捕捉任何数据模态之间的关系,而无需在架构中引入显式的数据特定偏见。与其说是一个能够直接处理不同数据模态的神经网络架构,不如说是需要仔细考虑数据输入结构,并设计合适的训练任务目标,才能成功构建一个高效的变换器。即使对于当前的最先进(SOTA)架构,预训练的好处仍然适用。预训练这一过程是“迁移学习”概念的一部分,迁移学习将在监督学习和无监督学习章节中有更详细的讨论。目前,变换器可以执行数据生成和监督学习任务,并且越来越多的研究在探索变换器应用于未开发的细分任务和数据模态。期待未来几年中,变换器作为技术进步的前沿,带来更多深度学习的创新。
到目前为止,你已经获得了根据你的数据和需求适当地选择和设计神经网络架构所需的知识。在第二章到第六章中介绍的大多数架构和概念从零实现起来是很有挑战的,但借助像github.com/rwightman/pytorch-image-models这样的开源工作,以及许多深度学习框架的帮助,使用一个模型只是导入库并添加几行代码的问题。实际上,理解每个架构的底层实现并不是使用这些模型所必需的,因为采用公开可用的工作(包括在大数据集上预训练的权重)非常方便。今天,大多数情况下,理解像 CNN 这样的架构概念的大致原理,并且知道一些训练模型的基本内容,实际上就足以从这些几乎可以直接使用的架构/模型中获得实际好处。然而,理解这些架构的实现细节是至关重要的,在以下情况下将证明其价值:
-
当事情失败或未按预期工作时(模型未收敛到最优解或发生发散,模型错误与准备输入数据的形状不符,等等)。
-
当需要根据你的数据集、运行时要求或性能要求选择一个更合适的架构时。
-
当你发明一个崭新的神经网络层时。
-
当你想解码神经网络实际上学习了什么,或者当你想理解神经网络为什么做出某个预测时。这将在第十一章《解释神经网络预测》和第十二章《解释神经网络》中进行更深入的探讨。
-
将一个架构领域的概念应用到另一个领域。例如,DenseNet 和 ResNet 的跳跃连接可以轻松地转移到 MLPs 中。
既然你已经读完了这一章,这一章完成了神经网络架构的具体内容,给自己点个赞吧。你现在已经掌握了处理图像(CNNs)、时间序列或序列数据(RNNs)、处理表格数据(MLPs)以及万金油(transformers)模型的知识。然而,架构的性能与数据类型、数据结构、数据预处理方法、权重学习与优化方法、损失函数以及优化任务密切相关。关于这些组件的更多细节将在第八章《探索监督式深度学习》和第九章《探索无监督深度学习》中解读。
在下一章,我们将探讨一种新兴的方法,通过神经架构搜索自动设计神经网络模型。
第七章:深度神经架构搜索
前几章介绍并回顾了不同的神经网络(NNs),这些网络被设计用来处理不同类型的数据。设计这些网络需要的知识和直觉是通过多年在该领域的研究才能获得的。大部分网络是由专家和研究人员手工设计的。这包括发明全新的 NN 层,并通过组合和堆叠已存在的 NN 层来构建一个实际可用的架构。这两个任务都需要大量的迭代实验时间,才能成功创建出一个有用的网络。
现在,想象一个我们可以专注于发明有用的新型层,而软件负责自动化最终架构构建过程的世界。自动化架构搜索方法正是通过简化设计最佳最终 NN 架构的任务来实现这一目标,只要根据深厚的领域知识选择合适的搜索空间。在本章中,我们将重点介绍如何通过一种叫做 神经架构搜索(NAS)的自动化架构创建过程,从已有的 NN 层构建一个实际可用的架构。通过理解不同类型的 NAS,你将能够根据当前的模型构建设置,选择最简单的自动化搜索优化方法,从简单到高效复杂都有涉及。具体来说,以下主题将被介绍:
-
理解 NAS 的大局
-
理解基于一般超参数搜索的 NAS
-
理解基于 强化学习(RL)的 NAS
-
理解非 RL 基于的 NAS
技术要求
本章包括使用 Python 编程语言的实际实现。这些简单的方法需要安装以下库:
-
numpy -
pytorch -
catalyst ==21.12 -
scikit-learn
你可以在 GitHub 上找到本章的代码文件,地址为 github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_7。
理解 NAS 的大局
在我们深入探讨 NAS 方法的全貌之前,重要的是要注意,尽管 NAS 减少了构建最终架构所需的手动工作,但它并没有完全消除该领域专业知识的需求。正如我们之前讨论的,深度学习(DL)的基础知识对于选择合适的搜索空间并准确解读 NAS 的结果至关重要。搜索空间是搜索过程中可以探索的可能选项或配置的集合。此外,NAS 的性能在很大程度上依赖于训练数据的质量以及搜索空间与任务的相关性。因此,领域专业知识仍然是必要的,以确保最终架构不仅高效,而且准确且与解决的问题相关。通过本节内容的学习,你将更好地理解如何利用你的领域专业知识来优化 NAS 的效果。
前几章关于神经网络(NN)的内容仅介绍了少数几种显著的神经网络层类型,并且只是略微触及了现有神经网络层库的表面。如今,神经网络层的变种非常多,这使得精确设计在架构中的各个环节使用哪些层变得困难。主要问题在于,可能的神经网络架构空间是无限大的。此外,评估任何可能的架构设计需要耗费大量的时间和资源。这些因素使得评估所有可能的神经网络架构变得不可能。以在 ImageNet 上训练卷积神经网络(CNN)ResNet50 架构为例,来感受这种不可能性的程度。仅使用一块 RTX 3080 Ti Nvidia GPU(这是一款面向普通消费者的 GPU,且可以在市场上购买到)进行训练,大约需要 3-4 天的时间。另一方面,商业消费者通常使用工业级的 GPU 变体,这些 GPU 具有更强的处理能力,可以将训练时间缩短至一天以内。
通常,研究人员会凭直觉手动设计已经存在的神经网络(NN)层和操作。这种手动方法是一次性的努力,当出现更新、更好的核心神经网络层时,反复进行这种操作并不可扩展。这就是神经架构搜索(NAS)发挥作用的地方。NAS 利用已经发明的神经网络层和操作来构建更高效的神经网络架构。NAS 的核心在于通过更智能的方式在不同架构之间进行概念上的搜索。NAS 的搜索机制可以通过三种方式实现,即:一般超参数搜索优化、强化学习(RL)以及不使用强化学习的 NAS 方法。
通用超参数搜索优化方法适用于任何机器学习(ML)算法的超参数优化。强化学习(RL)是另一种高级机器学习方法,与监督学习(SL)和无监督学习(UL)一起,它涉及某种形式的优化,在一个环境中采取的行动会产生具有可量化奖励或惩罚的状态。非 RL 基础的 NAS 方法可以进一步分为三种不同类型:从小的架构基线逐步增长的架构、从复杂的完全定义架构图逐步缩小的架构,以及进化算法。逐步增长架构的方法包括所有将简单网络逐渐发展成更大网络的算法,增加其深度或宽度。反之,也有一些方法首先定义一个包含所有可能连接和操作的架构,然后逐步去掉这些连接。最后,进化算法是一类基于生物现象(如变异和繁殖)的算法。在本章中,我们将只介绍一些通用超参数搜索优化方法、RL 方法、基于逐步增长的简单 NAS 形式,以及逐步缩小的 NAS 的竞争版本。技术实现将提供给基于逐步增长的 NAS 方法,但其他更复杂的方法将不涉及,更多复杂方法的开源实现将另行参考。
在深入研究上述提到的 NAS 方法之前,你需要先了解微架构和宏架构的概念。微架构指的是在逻辑模块中所使用的层组合的具体细节。正如在第三章《理解卷积神经网络》中介绍的那样,一些逻辑模块可以重复堆叠在一起,以生成实际使用的架构。最终生成的架构中也可能包含不同的逻辑模块和不同的层配置。相比之下,宏架构指的是从更高层次上概述不同模块如何组合形成最终的神经网络架构。NAS 方法的核心思想总是围绕着基于已经整理好的知识来减少搜索空间,这些知识帮助我们知道哪些层或哪些层配置效果最佳。本章将介绍的这些方法,要么在保持宏架构不变的情况下,只在微架构空间中进行搜索,要么具有灵活性,可以通过创新技巧探索微架构和宏架构空间,从而使搜索变得可行。
首先,让我们从最简单的 NAS 方法开始,它是通用超参数搜索优化算法。
理解基于通用超参数搜索的 NAS
在机器学习中,参数通常指的是模型在训练过程中学习到的权重和偏差,而超参数是训练开始前设定的值,影响模型的学习方式。超参数的例子包括学习率和批量大小。通用的超参数搜索优化算法是一种 NAS 方法,用于自动搜索用于构建给定神经网络架构的最佳超参数。让我们来了解一些可能的超参数。在多层感知机(MLP)中,超参数可能是控制 MLP 深度的层数、每层的宽度以及使用的中间层激活函数类型。在卷积神经网络(CNN)中,超参数可能是卷积层的滤波器大小、每层的步幅大小以及每个卷积层后使用的中间层激活函数类型。
对于神经网络架构,可配置的超参数类型在很大程度上取决于用于创建和初始化神经网络的辅助工具和方法的能力。例如,考虑单独配置三层隐藏层的任务。如果有一种方法可以生成一个具有固定三层的多层感知机(MLP),那么就可以只对隐藏层大小进行超参数搜索。这可以通过简单地向函数中添加三个超参数来实现,分别设置三层隐藏层的大小。然而,要想实现既能进行层数的超参数搜索,又能进行隐藏层大小的搜索,就必须构建一个辅助方法,动态地应用这些超参数来创建一个 MLP。
最简单的神经架构搜索(NAS)形式利用这些工具来对已定义的超参数进行稍微智能的搜索。这里将介绍三种广为人知的超参数搜索变体;其中包括pytorch,因为其实现简短,足够在一章内介绍。让我们从连续二分法开始。
使用连续二分法搜索神经网络架构
最基本的通过缩小搜索空间来优化运行时间的方法是随机采样一些超参数配置,仅对采样的配置执行完整的训练和评估。这种方法简单地称为随机搜索。如果我们知道某些配置在消耗一定资源后几乎肯定会表现差呢?
连续二分法是随机搜索的一种扩展,它有助于在寻找最佳神经网络架构时节省资源。连续二分法的理念是在每一步中淘汰表现不佳的一半配置,从而使我们能够集中精力在更有前景的配置上。这样,我们就不会浪费时间在那些不太可能产生良好结果的配置上。
让我们通过一个简单的例子来分解这个概念。假设你正在尝试为一个 MLP 找到最佳配置,该配置包含不同的超参数,如层数和每层的大小。你从随机抽取 100 个不同的配置开始。现在,不是将所有 100 个配置训练到完成,你应用了逐步缩减(successive halving)。你将每个配置训练一段短时间(例如 5 个 epoch),然后评估它们在验证集上的表现。此时,你会淘汰表现最差的 50 个配置,继续训练剩下的 50 个配置,再进行 5 个 epoch。
在第二轮训练后,你再次评估剩余配置的性能,并淘汰表现最差的 25 个配置。然后,前 25 个配置可以继续训练,直到收敛。通过应用逐步缩减,你通过专注于最有前景的配置,同时早期淘汰表现不佳的配置,节省了资源和时间。这使得你能够更高效地寻找问题的最佳神经网络架构。
让我们深入研究逐步缩减的技术实现,这将为基于超参数调整的 NAS 下的所有其他方法奠定基础:
-
让我们首先导入相关的库,并设置
pytorch库的随机种子,以确保结果的可复现性:import json import os import numpy as np import torch import torch.nn as nn import torch.nn.functional as F from catalyst import dl, utils from catalyst.contrib.datasets import MNIST from sklearn import datasets from sklearn.metrics import log_loss from sklearn.model_selection import train_test_split from sklearn.preprocessing import MinMaxScaler from torch import nn as nn from torch import optim from torch.utils.data import DataLoader, TensorDataset torch.manual_seed(0) -
接下来,让我们定义一个
pytorchMLP 类,该类具有根据隐藏层的数量和每层的隐藏层大小动态构建 MLP 的功能:class MLP(nn.Module): def __init__(self, input_layer_size, output_layer_size, layer_configuration, activation_type='relu'): super(MLP, self).__init__() self.fully_connected_layers = nn.ModuleDict() self.activation_type = activation_type hidden_layer_number = 0 for hidden_layer_idx in range(len(layer_configuration)): if hidden_layer_idx == 0: self.fully_connected_layers[ str(hidden_layer_number) ] = nn.Linear( input_layer_size, layer_configuration[hidden_layer_idx] ) hidden_layer_number += 1 if hidden_layer_idx == len(layer_configuration) - 1: self.fully_connected_layers[ str(hidden_layer_number) ] = nn.Linear( layer_configuration[hidden_layer_idx], output_layer_size ) else: self.fully_connected_layers[ str(hidden_layer_number) ] = nn.Linear( layer_configuration[hidden_layer_idx], layer_configuration[hidden_layer_idx+1] ) hidden_layer_number += 1 def forward(self, x): for fc_key in self.fully_connected_layers: x = self.fully_connected_layersfc_key if fc_key != str(len(self.fully_connected_layers) -1): x = F.relu(x) return x -
接下来,我们需要为该 MLP 编写训练逻辑,并提供一个特定的层配置列表。在这里,我们将使用名为
catalyst的pytorch抽象库来训练模型,并通过一些便捷的方法保存最佳和最后的训练模型:def train_and_evaluate_mlp( trial_number, layer_configuration, epochs, input_layer_size, output_layer_size , load_on_stage_start=False, best_or_last='last', verbose=False ): criterion = nn.CrossEntropyLoss() runner = dl.SupervisedRunner( input_key="features", output_key="logits", target_key="targets", loss_key="loss" ) model = MLP( input_layer_size=input_layer_size, layer_configuration=layer_configuration, output_layer_size=output_layer_size, ) optimizer = optim.Adam(model.parameters(), lr=0.02) checkpoint_logdir = "logs/trial_{}".format( trial_number ) runner.train( model=model, criterion=criterion, optimizer=optimizer, loaders=loaders, num_epochs=epochs, callbacks=[ dl.CheckpointCallback( logdir=checkpoint_logdir, loader_key="valid", metric_key="loss", mode="all", load_on_stage_start="last_full" if load_on_stage_start else None, ) ], logdir="./logs", valid_loader="valid", valid_metric="loss", minimize_valid_metric=True, verbose=verbose ) with open(os.path.join(checkpoint_logdir, '_metrics.json'), 'r') as f: metrics = json.load(f) if best_or_last == 'last': valid_loss = metrics['last']['_score_'] else: valid_loss = metrics['best']['valid']['loss'] return valid_loss -
接下来,我们需要一个方法来生成 MLP 的随机超参数。超参数结构为隐藏层大小的规格列表,其中列表中的项数决定了层数。我们将隐藏层的数量固定在 1 到 6 层之间,隐藏层的大小则在 2 到 100 之间:
def get_random_configurations( number_of_configurations, rng ): layer_configurations = [] for _ in range(number_of_configurations): layer_configuration = [] number_of_hidden_layers = rng.randint( low=1, high=6 ) for _ in range(number_of_hidden_layers): layer_configuration.append( rng.randint(low=2, high=100) ) layer_configurations.append(layer_configuration) layer_configurations = np.array( layer_configurations ) return layer_configurations -
现在,随着助手函数的定义完成,让我们设置我们的表格数据集来应用 MLP。这里将使用来自
scikit-learn的iris数据集。我们将加载数据集,缩放数据值,将数据集划分为训练集和验证集,并为catalyst库做准备。请注意,直到此步骤的代码将在接下来的两个方法中重复使用:iris = datasets.load_iris() iris_input_dataset = iris['data'] target = torch.from_numpy(iris['target']) scaler = MinMaxScaler() scaler.fit(iris_input_dataset) iris_input_dataset = torch.from_numpy( scaler.transform(iris_input_dataset) ).float() ( X_train, X_test, y_train, y_test ) = train_test_split( iris_input_dataset, target, test_size=0.33, random_state=42 ) training_dataset = TensorDataset(X_train, y_train) validation_dataset = TensorDataset(X_test, y_test) train_loader = DataLoader( training_dataset, batch_size=10, num_workers=1 ) valid_loader = DataLoader( validation_dataset, batch_size=10, num_workers=1 ) loaders = {"train": train_loader, "valid": valid_loader} -
我们在这里采用的连续二分方法是,将 epoch 作为资源组件,在预定义的 epoch 数量执行完后,执行三次连续二分迭代。表现最好的配置的前一半将在下一次迭代中继续训练。这里,我们使用 20 个初始配置,每个配置进行 3 次连续二分,每次 5 个 epoch。让我们首先定义这些值,并定义控制生成配置随机性的随机数生成器:
rng = np.random.RandomState(1234) number_of_configurations = 20 layer_configurations = get_random_configurations( number_of_configurations, rng ) successive_halving_epochs = [5, 5, 5] -
最后,我们将定义连续二分的执行逻辑。请注意,这里使用的是最后训练的 epoch 权重,而不是具有最佳验证得分的 epoch,下一次迭代时会使用这些权重:
for succesive_idx, successive_halving_epoch in enumerate(successive_halving_epochs): valid_losses = [] for idx, layer_configuration in enumerate(layer_configurations): trial_number = trial_numbers[idx] valid_loss = train_and_evaluate_mlp( trial_number, layer_configuration, epochs=successive_halving_epoch, load_on_stage_start=False if succesive_idx==0 else True ) valid_losses.append(valid_loss) if succesive_idx != len(successive_halving_epochs) - 1: succesive_halved_configurations = np.argsort( valid_losses )[:int(len(valid_losses)/2)] layer_configurations = layer_configurations[ succesive_halved_configurations ] trial_numbers = trial_numbers[ succesive_halved_configurations ] -
最佳配置可以通过以下逻辑来找到:
best_loss_idx = np.argmin(valid_losses) best_layer_configuration = layer_configurations[best_loss_idx] best_loss_trial_number = trial_numbers[best_loss_idx]
在连续二分中,一些配置可能只有在训练过程的后期阶段才能表现出色,而一些配置则可以在训练过程的早期阶段就表现出色。选择更长或更短的等待时间将使一些模型处于不利地位,并且需要找到一种我们可能尚不知道真相的平衡。接下来将介绍的 Hyperband 方法正是尝试解决这一问题。
使用 Hyperband 搜索神经网络架构
Hyperband 通过执行多个独立的端到端连续二分迭代(称为括号),改进了连续二分中的局限性。每个连续的括号将拥有更小的初始样本配置,但分配的资源更多。该算法本质上允许一些随机抽样的配置训练更长时间,从而增加了展示良好性能的固有潜力的概率,因此在后续的括号中放弃这些配置不会是浪费。完整的算法在图 7.1 中展示:

图 7.1 – Hyperband 算法伪代码
此算法需要两个用户输入配置:具体而言,R,表示训练和评估单个配置的最大资源量;以及 η,表示决定每次连续二分迭代结束时保留配置数量的除数。每个括号的总数 s_max、每个括号分配的总资源 B、每个括号和迭代的总配置数量 n 和 n_i 以及每个括号分配的资源 r_i 都通过公式计算。为了更容易理解,图 7.2 显示了当 R = 81 且 η = 3 时,每个括号中 Hyperband 结果配置示例:

图 7.2 – 每个括号中 Hyperband 结果配置示例
这些设置生成总共 5 个括号并生成总共 10 个最终模型。这 10 个模型中的最佳模型将作为搜索操作生成的最终模型。
注意,在此方法中,可以通过将模型的宏架构固定并仅搜索微架构逻辑块的超参数,将专家知识显式地注入到两种搜索方法的过程中。让我们通过在成功的减半主题中定义的方法和数据集实现 Hyperband 来进行实现:
-
首先,让我们定义在这里计算对数所需的额外库:
import math -
接下来,我们将定义 Hyperband 实现所需的两个输入参数,即我们希望在每个配置的最大资源(以 epochs 表示),以及在 Hyperband 算法中的每个成功的减半操作后的配置除数
N:resource_per_conf = 30 # R N = 3 -
现在,我们将根据图 7.1定义 Hyperband 的主要逻辑:
s_max = int(math.log(resource_per_conf, N)) bracket_resource = (s_max + 1) * resource_per_conf bracket_best_valid_losses = [] bracket_best_layer_configuration = [] for s in range(s_max, -1, -1): number_of_configurations = int( (bracket_resource / resource_per_conf) * (N**s / (s+1)) ) r = resource_per_conf * N**-s layer_configurations = get_random_configurations( number_of_configurations, rng ) trial_numbers = np.array( range(len(layer_configurations)) ) valid_losses = [] for i in range(s+1): number_of_configurations_i = int( number_of_configurations * N**-i ) r_i = int(r * N**i) valid_losses = [] for idx, layer_configuration in enumerate(layer_configurations): trial_number = '{}_{}'.format( s, trial_numbers[idx] ) valid_loss = train_and_evaluate_mlp( trial_number, layer_configuration, epochs=r_i, load_on_stage_start=False if s==s_max else True ) valid_losses.append(valid_loss) if succesive_idx != len(successive_halving_epochs) - 1: succesive_halved_configurations = np.argsort( valid_losses )[:int(number_of_configurations_i/N)] layer_configurations = layer_configurations[ succesive_halved_configurations ] trial_numbers = trial_numbers[ succesive_halved_configurations ] best_loss_idx = np.argmin(valid_losses) best_layer_configuration = layer_configurations[best_loss_idx] best_loss_trial_number = trial_numbers[ best_loss_idx ] bracket_best_valid_losses.append( valid_losses[best_loss_idx] ) bracket_best_layer_configuration.append( best_layer_configuration )
这两种方法在选择超参数配置时都广泛使用随机搜索,而不是很聪明。接下来,我们将探讨一种搜索方法,在进行了一些初始搜索后,优化下一个超参数选择的方法。
使用贝叶斯超参数优化搜索神经架构
贝叶斯超参数优化是一种利用代理性能估计模型选择估计最佳配置集以从中采样和评估的方法。正式地说,采样配置来训练和评估称为采集函数。贝叶斯优化尝试利用从初始随机配置采样和实际训练评估中获得的先验信息,找到预计表现良好的新配置。贝叶斯优化的步骤如下:
-
对一些超参数配置进行采样。
-
使用这些配置进行完整的训练和评估,以获取性能分数。
-
使用所有可用数据训练一个代理回归模型(通常使用高斯过程(GP)模型)来估计基于超参数的性能分数。
-
要么使用所有可能的超参数配置,要么随机采样大量超参数配置,并使用代理模型预测性能分数。
-
从代理模型中获取具有最小估计性能分数的k个超参数配置。
-
重复步骤 1到步骤 5,要么预先确定的次数,直到获得足够好的结果,要么直到资源预算用完为止。
该过程本质上试图通过估计将要产生的分数来加速实际的训练和评估过程,只有在估算的前几个最佳配置进行实际训练时才会进行训练。优化只有在代理模型性能评分估计函数显著快于实际训练和评估主模型时才有效。请注意,标准的贝叶斯优化只适用于连续空间,无法正确处理离散超参数。让我们通过之前定义的相同方法和数据集来回顾基于贝叶斯优化的 NAS 的技术实现:
-
让我们从导入贝叶斯优化方法背后的主要工具开始,这就是
scikit-learn中的 GP 回归器:from sklearn.gaussian_process import GaussianProcessRegressor -
接下来,让我们定义一个方法,创建一个结构化的固定大小为 6 的列,这是之前定义的最大可能层数。当层数少于 6 时,后面的列将只具有 0 层作为特征:
def get_bayesian_optimization_input_features( layer_configurations ): bo_input_features = [] for layer_configuration in layer_configurations: bo_input_feature = layer_configuration + [0] * (6 - len(layer_configuration)) bo_input_features.append(bo_input_feature) return bo_input_features -
接下来,让我们定义用于基于贝叶斯优化的 NAS 与 MLP 的三个重要参数。第一个参数是配置数。我们在这里采取的方法是根据每个配置的指定训练轮次,在第一次迭代时初步训练 100 个配置。之后,我们建立一个 GP 回归器来预测验证损失。然后,我们将在接下来的几次迭代中采样配置,并使用模型进行预测,选择前五个配置进行完整训练。在每次迭代中,都会使用所有可用的验证损失数据来构建一个新的回归器模型:
number_of_configurations = [100, 2000] epochs_per_conf = 15 topk_models = 5 -
最后,让我们定义实现基于贝叶斯优化的 NAS 版本的主要逻辑,使用 MLP:
Trial_numbers = np.array( range(len(layer_configurations)) ) trial_number = 0 model = None best_valid_losses_per_iteration = [] best_configurations_per_iteration = [] overall_bo_input_features = [] overall_bo_valid_losses = [] for number_of_configuration in number_of_configurations: valid_losses = [] layer_configurations = get_random_configurations( number_of_configuration, rng ) if model: bo_input_features = get_bayesian_optimization_input_features(layer_configurations) predicted_valid_losses = model.predict( bo_input_features ) top_k_idx = np.argsort( predicted_valid_losses )[:topk_models] layer_configurations = layer_configurations[ top_k_idx ] for idx, layer_configuration in enumerate(layer_configurations): trial_identifier = 'bo_{}'.format(trial_number) valid_loss = train_and_evaluate_mlp( trial_number, layer_configuration, epochs=epochs_per_conf, load_on_stage_start=False, best_or_last='best' ) valid_losses.append(valid_loss) trial_number += 1 best_loss_idx = np.argmin(valid_losses) best_valid_losses_per_iteration.append( valid_losses[best_loss_idx] ) best_configurations_per_iteration.append( layer_configurations[best_loss_idx] ) bo_input_features = get_bayesian_optimization_input_features(layer_configurations) overall_bo_input_features.extend(bo_input_features) overall_bo_valid_losses.extend(valid_losses) model = GaussianProcessRegressor() model.fit( overall_bo_input_features, overall_bo_valid_losses )
至此,我们已经通过贝叶斯优化实现了 MLP 超参数搜索!
除了本章讨论的基于超参数搜索的 NAS 方法外,值得一提的还有三种其他方法:分层搜索、代理模型和进化算法:
-
分层搜索专注于在不同粒度级别上优化架构,允许更高效地探索搜索空间。
-
代理模型作为目标模型的轻量级近似,减少了在搜索过程中评估候选架构的计算成本。
-
最后,进化算法受自然选择过程的启发,可以应用于 NAS 问题,通过变异、交叉和选择操作来探索和优化架构。
在选择基于超参数搜索的 NAS 技术时,这些方法也可以作为参考。
使用通用超参数搜索方法的 NAS 提供了一种比纯粹的随机搜索或暴力搜索更智能的配置搜索方式。它最有帮助的时机是当你已经具备了基础设施,能够轻松选择不同的超参数,并且已经将该领域的专家知识内嵌在系统中。
然而,使用通用超参数搜索的 NAS 通常需要大量超出算法范围的工具来构建模型并正式化那些可以通过超参数可靠控制的辅助方法。除此之外,仍然需要掌握一定的知识,知道使用哪种类型的层,以及如何超出算法外构建神经网络(NN)模型的宏观和微观架构。
在下一节中,我们将介绍一系列 NAS 方法,这些方法更全面地涵盖了实现任何 NN 的 NAS 所需的所有步骤,称为基于 RL 的 NAS。
理解基于 RL 的 NAS
RL 是一类学习算法,旨在学习一种策略,使得智能体在与环境中的状态交互时能够做出连续的行动决策。图 7.3展示了 RL 算法的总体概述:

图 7.3 – RL 算法的总体概述
这一类算法最常用于创建智能机器人,这些机器人可以作为离线玩家与真实人类对战。在数字游戏的背景下,环境代表了智能体操作的整个设置,包括诸如游戏角色的位置和状态,以及游戏世界的条件等方面。另一方面,状态是给定时刻环境的快照,反映了游戏的当前状况。RL 的一个关键组成部分是环境反馈组件,它可以提供奖励或惩罚。在数字游戏中,奖励和惩罚的例子包括某种形式的竞争性评分系统、游戏内现金、升级系统,或有时通过死亡来进行负面惩罚。当应用到 NAS 领域时,状态将是生成的 NN 架构,而环境将是对生成的 NN 配置的评估。奖励和惩罚则是训练并在选定数据集上评估后的架构的延迟性能和度量性能。另一个关键组成部分是策略,即负责根据状态产生行动的组件。
请记住,在基于通用超参数搜索的 NAS 中,NN 配置样本的获取是基于随机采样的。在基于 RL 的 NAS 方法中,目标不仅是优化搜索过程,还包括基于先前经验产生 NN 配置的获取过程。然而,如何产生这些配置的方法在不同的基于 RL 的 NAS 方法中有所不同。
在这一部分,我们将深入研究与 NAS 相关的几种 RL 方法:
-
基于 RL 方法的创始 NAS
-
高效 NAS(ENAS)通过参数共享
-
Mobile NAS(MNAS)
让我们从基于 RL 方法的第一个创立的 NAS 开始。
理解基于 RL 的创始 NAS
RL 可以通过神经网络实现,在 NAS 的使用案例中,递归神经网络(RNN)用作在测试时间概率生成主 NN 配置所需的关键部分。Figure 7**.4 简略展示了采用 RL 方法的基础 NAS 的架构概述:

图 7.4 – 带有 RL 工作流的 NAS 的架构概述
RNN 是决定从先前环境交互中学习步骤后状态的策略。在 NAS 的情况下,动作等同于状态。回想一下,RNN 由多个顺序的递归单元组成,其中每个单元能够产生一个中间的顺序输出。在 NAS 中,这些中间的顺序输出被设计用来预测主 NN 的特定配置。然后,这些预测被作为单元输入馈送到下一个 RNN 单元中。考虑到在图像领域搜索最佳 CNN 架构的 NAS 任务。Figure 7**.5 展示了使用基于 RNN 的 NN 配置预测结构的任务结构:

图 7.5 – NAS 的基于 LSTM 的 CNN 层配置预测
卷积层有几个需要决定的规格:卷积滤波器的数量、卷积滤波器的大小和步幅的大小。Figure 7**.5 展示了单个 CNN 层的预测。对于后续的 CNN 层,相同的长短期记忆(LSTM)单元被重复顺序预测,上一个 LSTM 单元的状态和单元输出作为输入。对于四层 CNN,LSTM 将自回执行四次以获取所有所需的配置预测。
LSTM 的参数更新方式采用一种称为策略梯度的过程。策略梯度是一组使用梯度来更新策略的方法。具体而言,这里使用reinforce规则来计算更新参数的梯度。更易理解的术语是,以下公式展示了如何计算梯度:
梯度 = (所有抽样结构的交叉熵损失 x (奖励 − 先前奖励的移动平均)的平均)
这里的交叉熵损失专门用于强调配置预测任务被框架为一个多类分类问题,这样可以将搜索参数的数量限制在一个较小的范围内,同时确保边界被设置。例如,你不希望一个 CNN 层使用一百万个滤波器,或者一个 MLP 中的全连接层有一百万个神经元。
这里的强化学习(RL)过程是由探索与利用的概念引导的。如果我们仅仅继续使用 RNN 的预测状态,并将其作为计算交叉熵损失的标签,策略就会变得越来越偏向其自身的参数。将 RNN 预测作为标签被称为利用过程,其理念是让 RNN 对其自身的预测更有信心。这个过程使得模型在当前的智能水平上变得更加深刻,而不是朝着可以通过外部数据探索获得的智能方向发展。这里的探索是指在每个 RNN 单元中,网络配置是随机采样的,用来作为交叉熵损失的标签。这里的理念是从大量的探索开始,并逐渐减少探索,进入策略学习的后期,更多依赖于利用。
到目前为止,这些步骤仅允许相对简单的 CNN 形式,但可以向 RNN 代理中添加修改,以支持更复杂的 CNN 构建,例如来自 ResNet 或 DenseNet 的并行连接或跳跃连接。在原始方法中,尝试通过在图 7.5中展示的五个顺序 RNN 单元的末尾添加一个额外的单元来增加跳跃连接的复杂性,作为一种叫做tanh的激活函数,再乘以一个可学习的权重,最后应用一个 sigmoid 激活函数,将输出值限定在0到1之间。这里的关键信息是,sigmoid 函数提供一个概率值,允许执行“是否添加跳跃连接”的二分类任务。可以使用0.5的值来决定输出是1还是0。然而,一个问题是,不同层之间的输出大小可能不兼容。为了解决这个不兼容问题,会自动应用一种技巧,通过用零填充较小的输出特征图,以确保两个特征图具有相同的大小。
这种方法允许你在 NAS 中动态地向 CNN 添加跳跃连接。图 7.6展示了使用CIFAR-10图像数据集,通过这种 NAS 方法获得的最终架构:

图 7.6 – 从 https://arxiv.org/abs/1611.01578v2 论文中通过 NAS 与 RL 获得的 CNN
尽管架构简单,但它能够决定实现良好结果所需的最佳跳跃连接。由此产生的架构展示了架构的复杂性,也表明如果没有适当的搜索算法,人类手动设计出这种结果将是多么困难。再次指出,任何复杂性和修改都可以添加到 RNN 策略中,以考虑额外的组件,如学习率、池化方法、归一化方法或激活方法,这突显了这一思路的灵活性。此外,NAS 方法还可以应用于搜索 MLP 或 RNN 主神经网络。然而,这些额外的适配和复杂性在此不作讨论。
请注意,这项技术固定了微架构结构,因为它使用了标准的卷积层。然而,该技术通过允许跳跃连接,支持某种形式的宏架构设计。该基础技术的一个主要问题是评估随机生成或预测的架构配置所需的时间。接下来,我们将探索一种旨在最小化此问题的方法。
理解 ENAS
ENAS 是一种扩展基础 NAS 和 RL 方法的技术,通过提高生成架构的评估效率。此外,ENAS 提供了两种不同的方法,允许搜索宏架构或微架构。参数共享是一个与 迁移学习(TL)相关的概念,即从一个任务中学到的知识可以转移到另一个任务,并对该任务进行微调,以获得更好的结果。以这种方式训练和评估主要子架构提供了一种明显的加速过程的方法。然而,它在很大程度上依赖于从先前架构预训练的权重,并且即使最终搜索到的架构表现良好,也无法提供对最终架构的无偏评估。尽管如此,这种方法在与新颖的搜索空间结合时仍然证明是有价值的。
ENAS 也通过 RNN 应用 RL,但其搜索方向完全不同,并且通过 RNN 预测不同的组件。ENAS 使用的搜索空间是通过一个 有向无环图(DAG)实现的,其中节点的数量决定了子架构的层数。图 7.7 显示了一个四节点 DAG 的示例:

图 7.7 – 一个四节点的有向无环图(DAG)表示 ENAS 的搜索空间
然后,RNN 将作为控制器,预测任何架构类型的两个组成部分:即,连接哪个先前节点和使用哪个计算操作。在这种情况下,RNN 将自回归地预测这两个组成部分四次,以考虑四个节点。图 7.7中的红线显示了预测的先前节点连接。每个节点将有一个固定的计算操作可以选择。由于会进行计算操作的随机抽样以确保轨迹不偏,程序将基于相同的搜索空间。参数共享方法应用于这些节点的计算操作部分。在每次训练迭代后,每个层的每个计算操作的权重将被保存,以供将来进行参数共享使用。参数共享的工作方式是,每个节点号上的每个计算操作将作为标识符,在同一层和相同计算操作下再次使用时,保存并重新加载权重。
ENAS 可以应用于搜索 RNN 架构、CNN 架构和 MLP 架构,并且通常可以扩展到任何其他架构类型。以 ENAS 搜索 CNN 架构为例,对于 CNN,ENAS 引入了两种搜索方法;第一种是执行宏架构搜索,第二种是执行微架构搜索。在宏架构搜索中,提出了六种操作,这些操作包括大小为 3 x 3 和 5 x 5 的卷积滤波器、大小为 3 x 3 和 5 x 5 的深度可分离卷积、以及大小为 3 x 3 的最大池化和平均池化。这一操作集提供了更多的多样性,而不仅仅是普通的卷积层,但它没有允许卷积层配置值的更多动态变化,而是将相同的配置设定为固定值。这里的另一个实现细节是,当选择多个先前节点进行连接时,先前节点各层的输出会在深度维度上进行连接,然后发送到当前节点的层。图 7.8显示了使用CIFAR-10数据集进行宏架构搜索的结果:

图 7.8 – 使用宏架构搜索策略的 ENAS 结果
该结果仅使用 0.32 天,通过一块过时的 NVIDIA GTX 1080 Ti GPU 在CIFAR-10数据集上实现,虽然没有使用 ImageNet 数据集,但在CIFAR-10验证集上仅取得了 3.87 的错误率。
至于微架构搜索,思路是构建低级逻辑块并重复相同的逻辑块,以便架构能够轻松扩展。ENAS 中搜索了两种不同的逻辑块:一个由主要卷积操作组成的逻辑块,以及一个旨在减少维度的缩减逻辑块。图 7.9展示了用于在 ENAS 中扩展微架构决策的最终架构的宏架构:

图 7.9 – 扩展微架构逻辑块的最终宏架构结构
N是一个固定的数字,在整个搜索过程中保持不变。由于这是微架构,因此架构构建过程被设计成允许层之间进行更复杂的交互,特别是通过跳跃连接向前一节点的输出添加操作。由于这一点,对于卷积逻辑块,RNN 被调整,通过固定前两个 RNN 单元来指定连接到哪两个前置节点索引,接下来的 RNN 单元则预测要对这两个选择的前置节点索引分别应用的计算操作。至于缩减逻辑块,主要思路是选择任何操作并使用步幅为 2 的卷积,这有效地将输入的空间维度缩小了一半。缩减逻辑块可以与卷积逻辑块一起预测,使用相同数量的节点和相同的 RNN。微架构中的参数共享方法等效地从一般情况调整,通过类似的方式使用层数和计算类型作为标识符来保存和加载训练好的子架构权重。图 7.10展示了在 ENAS 中使用微架构搜索策略的结果:

图 7.10 – ENAS 微架构搜索策略的结果
该结果仅使用过时的 NVIDIA GTX 1080Ti GPU,在CIFAR-10数据集上完成,虽然没有使用 ImageNet 数据集,且在CIFAR-10验证数据集上的误差率仅为 2.89。
接下来,让我们介绍基于强化学习的最终 NAS 方法,称为 MNAS。
理解 MNAS
MNAS 是一种用于创建 CNN 架构的方法,称为 MnasNet,这是一种基于 CNN 的架构。MNAS 后来被用于构建在 第三章《理解卷积神经网络》中介绍的 EfficientNet 架构系列。然而,该方法仍然可以用于生成其他架构类型,如 RNN 或 MLP。MNAS 的主要目标是考虑延迟组件,这是用于边缘计算或移动设备架构时的主要关注点,正如其名称所示。MNAS 扩展了基于 RL NAS 的概念,并引入了一个比 ENAS 中的微架构搜索更灵活的搜索空间,从而允许在不同模块中创建更多样化的层,尽管宏架构是固定的。图 7.11 显示了一个 MNAS 固定宏架构布局,包含七个模块,同时允许每个模块中的不同层具有不同的配置。七模块结构来自于 MobileNetV2 架构:

图 7.11 – 固定的七个模块宏架构示例,以及每个模块的配置,这些配置将被随机采样或预测
这里使用 RNN 作为控制网络,以预测每个 CNN 模块的以下配置:
-
卷积操作:标准卷积层、深度卷积层,以及来自 MobileNet 的 MBConv 层。
-
卷积滤波器大小:3x3 和 5x5。
-
压缩与激励比率:0,0.25。
-
跳跃连接操作:池化、恒等残差或没有跳跃连接。
-
每个模块的层数:0,+1,-1。该结构还参考了 MobileNetV2 中相同模块的层数。
-
每层输出滤波器大小:0.75,1.0,1.25。该结构还参考了 MobileNetV2 中相同位置卷积层的滤波器大小。
引入的搜索空间对提高网络效率和增加容量至关重要,以实现更好的度量性能。例如,在 CNN 中,大部分计算集中在较早的层,因为这些层的特征尺寸较大,相比于后期的层,需要更高效的层。
关于延迟的一个大问题是,它是一个依赖于软件和硬件环境的组件。例如,假设架构 A 在硬件和软件 C 上比架构 B 更快。当在另一个硬件和软件环境 D 上测试时,架构 B 可能比架构 A 更快。此外,架构的参数数量和每秒浮动点运算(FLOPs)规格,也是延迟的一个代理,延迟还依赖于架构的并行度和硬件的计算核心。基于这些原因,MobileNet 将延迟组件添加到奖励计算中,通过在手机的软件和硬件环境中客观评估它,结合度量计算和延迟。图 7.12 展示了整个 MNAS 过程的概览:

图 7.12 – MNAS 概述,一种具有延迟保证的平台感知 NAS
延迟可以在实际的目标软件和硬件环境中计算,而不仅仅局限于使用手机。奖励是通过公式计算的,可以输入所需的目标延迟(以秒为单位):
reward = accuracy x [ 延迟 - 目标延迟 ] ^ w
MnasNet 的另一个关键细节是,OpenAI 使用了另一种叫做近端策略优化(PPO)的策略梯度方法来训练 RNN 策略网络,而不是强化方法。PPO 是一种方法,它相比标准的强化学习策略梯度,完成了以下两件事:
-
对策略进行较小的梯度更新,使策略以更稳定的方式学习,从而能够实现更高效的收敛
-
使用生成的概率本身作为随机样本生成的采样概率,从而自动平衡探索与利用
第一点通过两种方式实现:
-
使用当前演员网络的概率与参数更新前由演员网络生成的旧概率来加权损失
-
将概率裁剪到区间 [1 - ϵ , 1 + ϵ ],其中 ϵ 可以变化,但使用了 0.2 的值
该方法通过两个网络而非一个网络执行,分别称为演员网络和评论员网络。评论员网络的结构是预测一个不受约束的单一值,该值作为生成架构的评估逻辑的一部分,并与度量性能的奖励一起使用。另一方面,演员网络是我们所知的主要网络,负责生成理想的网络架构配置。这两个网络都可以通过 RNN 实现。此过程在图 7.12中得到了很好的展示。两个网络参数在每个批次中共同更新。演员网络的损失可以通过以下公式计算:
损失 = 当前预测配置概率 _____________________________ 旧预测配置概率 x 优势
然后使用该损失的最小值和另一种带有剪切概率的损失版本作为最终损失。剪切损失定义如下:
剪切损失 = (当前预测配置概率 _____________________________ 旧预测配置概率,1 − ϵ,1 + ϵ) x 优势
这里的优势是一个自定义的损失逻辑,提供了对采样的子架构的量化评估数值,既考虑了奖励(使用度量性能),又考虑了评论员网络预测的单一值。在强化学习法中,指数加权移动平均(EMA)被用来处理之前的奖励。类似地,这里使用了一种 EMA 形式来减少不同时间步的优势。这个逻辑稍微更具科学性,但对于那些想了解更多的人,可以通过以下公式进行计算:
优势 = 折扣 第一时间步的距离 x 评论员和奖励评估折扣 = λγ
评论员和奖励评估 = (奖励 + γ(t+1 时刻的评论员预测值) − t 时刻的评论员预测值)
λ(lambda)和γ(gamma)是介于 0 和 1 之间的常量。它们分别控制每个时间步前进时优势折扣的权重衰减程度。此外,γ还控制着未来时间步中预测的评论员(critic)值的贡献。至于评论员网络的损失,它可以通过以下公式来定义:
评论员损失 = (优势 + 未来评论员值 − 当前评论员值)²
最终的总损失将是评论员损失和演员损失的总和。与传统的强化学习策略梯度相比,PPO 在效率和收敛性方面通常表现更好。这总结了 PPO 的逻辑,便于理解。
这里的强化学习搜索空间效率较低,直接在 64 个 TPUv2 设备上训练 ImageNet 大约需要 4.5 天。然而,这导致了一个名为 MnasNet 的子架构,其效率超过了相同准确度下的 MobileNetV2,或者在相同延迟下比 MobileNetV2 更准确,且在 ImageNet 上进行了基准测试。同样的 MNAS 方法最终被应用于 EfficientNet,成为今天最有效的 CNN 模型家族之一。
总结 NAS 与 RL 方法
强化学习(RL)为我们提供了一种通过采样、训练和评估神经网络架构来智能地学习最具表现力的神经网络(NN)架构的方法,并通过预测性地生成最有效的神经网络架构配置来应用所学的经验。简而言之,使用 RL 的神经架构搜索(NAS)训练一个神经网络来生成最佳的神经网络架构!使用 RL 的 NAS 最大的挑战仍然是所需的高昂计算时间。为了规避这个问题,下面列出了一些不同方法所采取的技巧:
-
在一个较小但仍具有代表性的数据集上训练 NAS 作为代理任务,并在主要的大数据集上训练和评估最终获得的神经架构。
-
通过唯一的层数和计算类型进行参数共享,幸运的是,这可以通用地适应 ENAS 的其他方法。
-
平衡宏观架构和微观架构的搜索灵活性,以减少搜索空间,同时确保它足够灵活,可以利用网络不同阶段所需的关键差异,从而实现高效性和良好的度量性能。
-
将目标延迟和实际延迟直接嵌入作为奖励结构的一部分,结果是只搜索大多数位于指定延迟范围内的架构。
请注意,这里介绍的方法并没有提供关于强化学习(RL)及其潜力的详尽概述。
尽管 RL 提供了实现 NAS 的具体方式,但它并不是绝对必要的。在接下来的部分,我们将通过一些不使用 RL 的 NAS 特定方法的示例。
理解非 RL 基础的 NAS
NAS 的核心在于通过基于先前搜索经验做出决策,智能地在不同的子架构配置中进行搜索,从而以非随机和非暴力的方式找到最佳的子架构。而 RL 的核心则是利用基于控制器的系统来实现这种智能。无需使用 RL 也能实现智能化的 NAS,在这一部分,我们将介绍一种简化版的逐步从零开始的 NAS 方法,它不使用控制器,以及另一种来自复杂完全定义的神经网络宏观架构和微观架构的竞争性消除方法。
理解基于路径消除的神经架构搜索(NAS)
首先,softmax 激活在加权求和过程之前。在测试时,选择节点间的前 k 条路径或操作作为实际网络,而其他路径则被剪枝。当权重向量更新时,子架构本质上发生了变化。然而,并不会训练和评估这个新的子架构以获取其在保留或验证数据集上的新度量性能,而是仅使用整个架构的一个训练周期,通过训练损失来估算最佳的验证性能。这个估算值将通过梯度下降更新超参数架构的参数。
BinaryConnect。这些二值化的权重充当门控,只有在启用时才允许数据通过。这一添加有助于缓解任何过度参数化架构的最大问题:需要的 GPU 内存来存储定义架构的参数。第二个补充是将延迟组件添加到整体损失中,这对于确保搜索考虑延迟并避免仅为获得更好的度量性能而尝试使用更多路径至关重要。让我们逐步揭示细节,首先描述代理 NAS 中使用的整体训练方法:
-
仅根据每个节点的单个随机采样路径,基于
softmax条件化的权重向量,使用训练数据集损失训练 BinaryConnect 权重向量。通过冻结架构的其余参数,并使用标准的交叉熵损失来实现这一点。 -
仅根据每条路径的概率,通过
softmax条件化的权重向量,基于验证数据集损失训练架构参数。这是通过冻结权重向量的参数,并使用架构参数的近似梯度公式来实现的:通过路径 1 的参数梯度 = 二进制权重 2 的梯度 x 路径 2 的概率 x (− 路径 1 的概率) + 二进制权重 1 的梯度 x 路径 1 的概率 x (1 − 路径 2 的概率)
通过路径 2 的参数梯度 = 二进制权重 1 的梯度 x 路径 1 的概率 x (− 路径 2 的概率) + 二进制权重 2 的梯度 x 路径 2 的概率 x (1 − 路径 1 的概率)
该公式计算路径 1 和路径 2 的梯度。所使用的损失是交叉熵损失,与修剪后的路径预测延迟相加,类似于 DARTS。延迟是通过一个外部机器学习模型预测的,该模型经过训练以根据架构的参数预测延迟,因为延迟评估占用了过多时间,通常需要多次运行的平均值才能得到可靠的估计。任何机器学习模型都可以用来构建延迟预测模型,并且这只是 NAS 过程开始之前的一次性操作。
-
重复步骤 1-2,直到收敛,达到预定的训练周期数,或在验证损失上没有改进时提前停止,且停止的周期数是预设的。
请记住,BinaryConnect 用于实现充当门控的二进制权重。一个细节是,标准的无约束非二进制权重向量仍然存在,但会应用二值化操作。二值化过程通过以下步骤执行:
-
将所有二进制权重设置为
0。 -
使用
softmax条件化的权重向量作为概率,采样所需数量的选择路径。 -
将所选路径的二进制权重设置为
1。
-
-
BinaryConnect 通过仅将非零路径加载到内存中来节省内存。
PNAS 直接在 ImageNet 上实现了 85.1%的 top-1 准确率,而没有使用诸如CIFAR-10之类的代理数据集,仅使用 NVIDIA GTX 1080 Ti GPU 进行了 8.3 天的搜索。
接下来,我们将介绍一种简单的基于渐进增长的 NAS 方法。
理解基于渐进增长的 NAS(Progressive NAS)。
基于渐进增长的 NAS 方法的关键区别在于,该方法可以在宏观架构和微观架构中都不受限制地构建。本章介绍的大多数技术在一般结构方面都有很多领域知识的投入,被发现是有用的。基于增长的 NAS 在搜索空间方面自然是非有限的,可能有助于发现效果良好的新型宏观架构结构。NAS 的这一路线将继续发展到可以与其他 NAS 方法竞争的阶段,但在本书中,我们将只介绍一种搜索名为渐进 NAS(PNAS)的子架构微观结构的方法。
PNAS 通过在 NAS 中采用基于渐进增长的方法简单地使用在本章前面介绍的贝叶斯优化概念,并在固定宏观架构结构的同时在微观架构水平上进行搜索,类似于 ENAS 微架构搜索方法。宏观架构结构适应数据集的大小,如CIFAR-10采用较小的结构,而 ImageNet 采用较深的结构。图 7**.13显示了这些结构:

图 7.13 – 来自 https://arxiv.org/abs/1712.00559v3 论文的 PNAS 宏观架构结构
该方法可以通过以下定义的步骤来完成,同时在单元格中有一个初始预定义的最大块数,并以零块开始:
-
从第一个块开始。逐步构建所有单元格选项的完整 CNN,并评估所有 CNN。
-
训练一个 RNN 代理模型,以预测使用单元格配置的指标性能。
-
扩展到下一个块,并预测可能的单元格选项组合的下一个块的指标性能,使用所有已选择和评估的先前块变体。
-
选择前两个最佳指标性能单元格选项用于下一个块,并训练和评估使用这两个单元格选项的完全构建的 CNN。
-
使用在步骤 4获得的额外两个数据点对 RNN 代理模型进行微调。
-
重复步骤 3到步骤 5,直到总块数达到最大块数或者指标性能不再改善为止。
每个单元格中的块都将有一个配置,这个配置定义了五个变量,类似于 ENAS;即,第一个输入、第二个输入、第一个输入的操作、第二个输入的操作,以及组合第一个输入操作和第二个输入操作输出的方法。可能的输入集包括所有先前的块、前一个单元格的输出,以及前一个单元格的前一个单元格的输出。这意味着该单元格可以与其他单元格进行交互。所有可能的输入组合、操作类型和组合方法都被列出在每个逐步的块步骤中,并传递给 RNN 模型以预测指标性能。
PNAS 成功地在 ImageNet 上实现了 84.2 的 top-1 准确率,但使用了 225 个 GPU 天,使用的 GPU 是 NVIDIA GTX 1080。
基于逐步增长的 NAS 方法已经发展到可以通过一种叫做高效前向架构搜索的方法,仅用 5 天的搜索就能实现 84 的 top-1 准确率,但这超出了本书的讨论范围。
本章结束时,图 7.14展示了图像领域中所有已介绍方法的性能总结比较,不包括通用超参数搜索方法:
| 方法 | 参数数量(百万) | 搜索时间(天) | CIFAR-10 测试误差 | ImageNet 测试误差 |
|---|---|---|---|---|
| Vanilla RL NAS | 4.2 | 1680 | 4.47 | N/A |
| ENAS 宏 | 21.3 | 0.32 | 4.23 | N/A |
| ENAS 微 | 4.6 | 0.45 | 2.89 | N/A |
| DARTS | 3.4 | 4 | 2.83 | 26.9 |
| Proxyless NAS | 7.1 | 8.3 | N/A | 24.9 |
| PNAS | 5.1 | 225 | 3.41 | 25.8 |
图 7.14 – 所有已介绍的 NAS 方法的性能比较,不包括通用超参数搜索方法
本表格包括每个已介绍的 NAS 方法在CIFAR-10和 ImageNet 数据集上的参数数量、搜索时间和测试误差率。每个 NAS 方法在延迟、复杂度和准确性方面各有优缺点。特别是 ENAS 微方法,具有相对较少的参数、较短的搜索时间和较低的CIFAR-10测试误差率。它可能是图像领域神经架构搜索的推荐选择。然而,具体的选择依赖于项目的需求和约束,例如可用的计算资源和期望的准确性。
总结
NAS 是一种可以推广到任何类型神经网络(NN)的方法,允许自动创建新的和先进的神经网络,而无需手动设计神经网络架构。正如你可能猜到的,NAS 在基于图像的神经网络领域占据主导地位。EfficientNet 模型系列展示了 NAS 对基于图像的神经网络领域所带来的影响。这是因为与简单的 MLP 相比,CNN 组件种类繁多,设计起来更为复杂。对于序列或时间序列数据处理,RNN 单元的变化不多,因此 NAS 在 RNN 上的主要工作集中在设计自定义的循环单元上。为了适应当前最前沿的 Transformer 技术,还可以做更多工作,因为 Transformer 能够适应多种数据模态。
NAS 主要被大型机构中的研究人员或从业者采用。从业者在尝试为他们的用例训练更好的模型时,最关心的一个关键特性是达到最终结果的速度。NAS 本身仍然是一个需要数天完成的过程,如果应用于大规模数据集,可能需要几个月。这使得大多数从业者直接使用 NAS 变得困难。相反,他们通常使用已发布的开源实现中的现有架构。与手动定义的架构相比,使用现有架构在速度上没有差异,因此它给从业者提供了使用现有架构的动力。众所周知,预训练有助于提高模型的性能,因此直接使用 NAS 意味着你还需要在一个大型的通用数据集上预训练生成的架构,这进一步延长了完成 NAS 过程所需的时间。机器学习中的用例通常需要大量时间来探索问题设置,并找出从可用数据集中能够实现的潜在性能。因此,模型实验之间的快速迭代对于项目的成功至关重要。实验进展缓慢会延迟成功的识别。这些原因解释了为什么 NAS 主要被大型机构的从业者或那些愿意花时间设计可以跨领域使用的通用自定义神经架构的研究人员所采用,而不是为特定用例构建定制的架构。
然而,NAS 无疑仍为你的用例提供了一种独特的方式,去寻找独特的自定义架构,只要时间不成问题,并且目标是要么在特定延迟下最大化性能,要么通常获取最佳性能的模型,而不考虑延迟。
在下一章中,我们将详细讨论监督学习(supervised DL,SDL)中的不同问题类型,并提供一些通用的技巧和建议。
第八章:探索监督深度学习
第二章到第六章探讨了深度学习(DL)技术背后的核心工作原理,并包括了一些最基础的技术实现,便于理解。理解不同神经网络(NNs)工作原理的细节是很重要的。一个原因是,当任何神经网络模型出现问题时,你能够识别出根本原因并加以解决。这些章节也很重要,因为它们展示了深度学习架构如何灵活地解决各种现实问题。但这些问题到底是什么?我们应该如何在不同情况下有效训练深度学习模型?
本章将尝试具体回答前面提到的两个问题,针对监督深度学习进行讨论,但针对无监督深度学习的问题会留到下一章。本章将涵盖以下主题:
-
探索监督学习的应用场景和问题类型
-
实现神经网络层以解决基础问题类型
-
高效训练监督深度学习模型
-
探索实现和改进基于监督深度学习的解决方案的一般技术
-
分析监督深度学习中的多任务范式
技术要求
本章包括一些 Python 编程语言中的实际实现。为了完成这些内容,你需要一台安装了以下库的计算机:
-
pytorch -
catalyst==22.04 -
numpy -
scikit-learn
你可以在 GitHub 上找到本章的代码文件,链接:github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_8。
探索监督学习的应用场景和问题类型
监督学习需要标签数据。标签、目标和真实值指的都是同一件事。提供的标签本质上监督着机器学习(ML)模型的学习过程,并为深度学习(DL)模型生成梯度和更新自身提供反馈。标签可以以多种形式存在,包括连续数值格式、类别格式、文本格式、多类别格式、图像格式、视频格式、音频格式以及多目标格式。所有这些都可以被归类为以下监督问题类型之一:
-
二分类:这是指目标只有两个唯一值的分类数据。
-
多分类:这是指目标具有多个唯一值的分类数据。
-
回归:这是指目标具有连续的数值数据。
-
多目标/问题:
-
多标签:这是指目标与单一数据行关联的多个二进制标签。
-
多回归:这是指目标与单一数据行关联的多个回归目标。
-
多个问题:每个数据行在单一数据集中关联多个目标,或者是一个序列性质的问题链,通过不同数据集学习多个模型。
-
-
监督式表示学习:这可以有多种形式,主要目标是根据输入数据学习有意义的数据表示。学习到的表示结果可以随后的多种用途,包括迁移学习(TL),以及实现推荐系统。
这些问题的定义可能单独理解起来比较困难。为了更好地理解不同的问题,我们将查看一组广泛的应用场景,这些应用场景可以利用深度学习技术。根据之前指定的问题,以下表格列出了它们的应用场景:
| 问题类型 | 监督式深度学习模型 应用场景 |
|---|---|
| 二分类 |
-
胎儿性别预测通过超声影像
-
半导体芯片质量拒绝预测,通过图像数据进行制造过程中的判断
-
使用包含文本、图像、文档、音频或任何数据的电子邮件数据来预测垃圾邮件
|
| 多分类 |
|---|
-
文档数据主题分类
-
仇恨/有害言论或文本分类
-
一般图像目标分类
-
文本或语音数据的情感预测
|
| 回归 |
|---|
-
点击率(CTR)预测,通过图像、文本或二者结合的方式进行广告预测
-
人类年龄预测通过面部输入图像
-
从图像预测 GPS 位置
|
| 多目标 |
|---|
-
文本主题分类:多标签,单一文本数据行中可能包含多个主题。
-
图像目标检测:一个多目标问题,由多个回归目标和多类分类组成。首先,通过图像边界框作为多个回归目标,一个单一的x和y数值坐标,以及它的宽度和高度作为两个额外的目标,形成一个矩形形状的边界框。接下来,将使用这个边界框提取裁剪图像,用于多分类目的来预测物体的类型。
-
图像分割:一种多标签问题,每个像素都会作为二进制目标。
|
| 监督式表示学习 |
|---|
-
面部特征表示用于面部识别。
-
使用K-最近邻(KNN)进行说话人识别的音频表示。
-
使用方法分类嵌入(categorical embeddings)来表示类别,每个类别在分类特征列中都有一个代表性特征向量。这个方法是可学习的,并作为查找表。与基本的独热编码相比,这种方法能够减少高基数分类数据的特征维度,但仍能保持相似的性能。
|
表 8.1 – 深度学习问题应用场景表格
二分类、多类分类和回归问题的处理相对直接。然而,多目标类型的问题设置较为复杂,需根据问题的性质进行更多的架构设计。多目标任务也可以是直接的,例如多标签或多回归问题。该任务属于更广泛的多任务解决方案的范畴,将在本章的探索实现和改进基于深度学习的解决方案部分进一步讨论。
接下来,我们将实现实现基础问题的基本神经网络层,包括二分类、多类分类和回归问题。
实现基础问题类型的神经网络层
在第二章到第七章中,尽管介绍了许多类型的神经网络层,但针对问题类型的核心层要么没有使用,要么没有解释。这里,我们将逐一讲解每一层,以便更清晰地理解和掌握直觉。
实现二分类层
二分类意味着类别数据有两个选项。请注意,这并不一定意味着原始数据中的类别严格表示为真或假,或者是正面或负面。两个选项可以是原始数据中的任何格式,可以是字符串、数字或符号。然而,请注意,神经网络始终只能产生数值输出。这意味着目标本身必须以数值形式表示,其中最优的数值是零和一的二进制值。这意味着,用作训练目标的数据列如果只有两个唯一值,必须经过预处理,将其映射为零或一。
通常,在神经网络中有两种方式来定义二进制输出。第一种是使用一个大小为一的线性层。第二种方法是使用一个大小为二的线性层。就任务质量指标而言,这两者之间没有显著区别,但方法一在存储和内存方面稍微占用更少的空间,因此可以随时使用这种版本。方法一的输出将通过使用 sigmoid 层约束在0到1之间。对于方法二,输出需要传入 softmax 层,这样两个输出的概率加起来为一。两种方法通常都可以通过交叉熵来优化。交叉熵也被称为对数损失。对数损失通过对数刻度衡量预测概率与真实标签之间的差异。该刻度对错误预测进行更严厉的惩罚,强调模型能够为正确类别分配高概率,为错误类别分配低概率的重要性。
将方法一中的层转换为实际的pytorch代码,将使用torch中的nn模块,代码如下所示:
from torch import nn
final_fc_layer = nn.Sequential(
nn.Linear(10, 1),
nn.Softmax(),
)
接下来,我们将实现多类分类层。
实现多类分类层
针对一个有 10 个 logits 的 100 类多分类问题,pytorch 代码如下所示:
final_fc_layer = nn.Sequential(
nn.Linear(10, 100),
nn.Softmax(),
)
多分类问题的另一个子问题是类别具有顺序关系时。这种子问题和任务被称为顺序分类。这意味着类别之间存在增量关系。一个普通的多分类层策略会将顺序类别表现为不理想,因为多分类中的类别被认为彼此之间具有平等关系。这里的一种好策略是使用基于多标签分类任务的技术,这是一个多二分类任务。
假设我们有五个顺序类别,简化起见,将其表示为从 1 到 5 的数字。在实际应用中,这可以通过任何类别数据来表示。在神经网络中,这种情况将使用五个二分类头,其中类别将按升序依次分配到各个头。这个神经网络的原始预测将以一种方式被使用,最终预测的顺序类别将从最远连续的正二分类预测的位置中得出。一旦出现负预测,右侧其余的预测头将被忽略。图 8.1 通过模拟五个二分类头的输出预测来描述这一策略:

图 8.1 – 使用五个二分类头的输出预测进行顺序分类处理策略
学习过程将如常使用交叉熵损失来处理多个二进制目标。此外,可以使用不依赖于概率的强健指标(如召回率或精度)来监控每个 epoch 的性能。顺序编码方法使得模型能够学习目标之间的顺序关系。
接下来,我们将深入探讨回归层的实现。
实现回归层
pytorch 代码将如下所示,使用来自 torch 的 nn 模块:
final_fc_layer = nn.Linear(10, 1)
0 和 1。结合目标值的缩放,边界可以通过使用 sigmoid 层在神经网络中强制执行,该层也会将激活值缩放到 0 和 1 之间。在推理阶段,预测值可以通过将 0 和 1 之间的缩放值映射到先前指定的已知最小和最大边界,从而恢复为实际值。
无界方法通过允许外推来实现某种形式的泛化,而有界方法则允许在神经网络中加入已知的偏置。两种方法各有优缺点,因此选择哪种方法需要根据具体情况进行评估。
接下来,我们将深入探讨表示层的实现。
实现表示层
大多数方法侧重于不同数据模态的架构之间的相互作用或优化所代表特征的训练方法。这些是我们将在接下来的话题中进一步深入讨论的主题。一个真正代表表示层的关键层类型是嵌入层。嵌入层是一种将分类数据类型映射为可学习向量的层结构。通过这一层,每个类别将能够学习到一个能够与指定目标很好地对应的表示。该方法可用于将文本单词标记转换为更具代表性的特征,或者简单地作为独热编码的替代。分类嵌入使得对分类数据类型进行特征工程处理变得可能。独热编码生成的编码强制所有类别之间的距离相同。然而,分类嵌入允许根据其与目标变量及其他数据(如果有额外数据存在)的交互来获得适当的距离。
然而,分类嵌入并不是所有机器学习用例的灵丹妙药,即使它们在训练后与实际神经网络模型分离,只起到特征化作用。它们有时可以在总体上表现得比独热编码更好,反之亦然,有时可能会比独热编码表现得更差。这种方法仍然是对任何包含分类数据输入的数据集进行实验的关键方法。
有效训练监督深度学习模型
在第一章,深度学习生命周期中,强调了 ML 项目具有循环生命周期。换句话说,在项目生命周期中进行了大量迭代过程。为了有效训练监督学习深度模型,基于不同条件应采取许多通用方向,但绝对突出于各种问题的一点是适当的工具化。工具化更常见地称为pytorch或keras与tensorflow,部署的便利性,使用不同度量标准比较模型的便利性,模型调优的便利性,良好的模型训练监视可视化,以及关于进展的良好反馈(可以通过消息和通知发送警报)。如果没有真正简化整个流程的先进工具,您可以专注于使模型运行良好的重要部分,而不必处理基础设施问题,如协调将模型保存到不同文件夹中的DataRobot,一种付费工具,然后开源工具如 MLflow、Kubeflow 或 Metaflow 将成为下一个最佳替代方案。一旦选择了工具,进行 DL 模型训练将变得轻松。我们将使用 MLflow 作为示例工具,在以下步骤中展示 DL 模型训练的一些有效方法:
-
数据准备
-
配置和调优 DL 超参数
-
执行、可视化、跟踪和比较实验
此外,我们将探讨在结束此主题之前构建模型时的一些额外提示。
让我们详细探讨每一个。
为 DL 训练准备数据
数据是任何 ML 模型的核心。数据最终决定了可达到的模型性能、最终训练模型的质量以及最终训练模型的有效性。在第一章,深度学习生命周期中,我们探讨了使数据集设置具备 DL 价值的条件,以及在获取数据时所需的特性,结合探索性数据分析(EDA)来验证因果关系和有效性。那里的一般想法是识别和添加对所需目标具有因果影响的额外特征和数据形式。在本节中,我们将更深入地讨论将数据转换为 DL 可训练状态的重要步骤,列在这里:
-
数据分区
-
数据表示
-
数据增强
为 DL 训练分区数据
我们将首先介绍数据分区的步骤。对于训练、验证和测试模型来说,有一个良好的数据分区策略对于获得良好的性能模型至关重要。训练分区将严格用于训练。验证分区将在训练过程中严格用于验证模型。在深度学习中,验证分区通常被用作指导信号,用来指示何时停止训练或提取出使用外部数据训练的最佳性能权重。由于验证数据会影响模型的学习过程,并且会向着训练外验证数据的性质导致过拟合的方向发展,测试分区将是最终用于验证经过训练模型的泛化能力的分区。测试分区也被称为保留分区。为了更安全地防止过拟合,并确保模型的泛化能力,验证分区也可以专门用于在模型训练后仅验证一次,而不是在每个时期都用于验证。然而,这种策略要求从原始训练分区创建一个较小的内部验证分区。以下图展示了两种不同的策略:

图 8.2 – 两种不同的交叉验证数据分区策略
这种数据分区的过程被称为交叉验证。前面的图展示了只有单个分区设置的简单交叉验证策略。这可能会导致模型的性能指标报告对特定的结果分区设置产生偏倚。结果的分区可能具有某种固有的分布或性质,使得结果在特定分区设置中表现特别好或特别差。当发生这种情况时,在部署阶段对性能期望不匹配会造成更多操作问题。为了安全地消除这种偏差可能性,通常使用k 折交叉验证来报告更全面的验证分数,这些分数可以更好地反映模型在实际情况下的性能。为了执行这种分区方法,需要从原始数据集中移除单一测试集,并且在不同顺序的k交叉验证训练和验证分区上平均验证分数。这在图 8**.3中更好地可视化:

图 8.3 – K 折交叉验证作为消除度量报告偏差的策略
最后,为了测试性能报告和部署目的,模型可以重新在训练和验证数据集合并或从第一折训练的模型中提取出用于部署目的。
记住,分层划分是将数据划分为三个提到的部分的推荐策略。这意味着数据将根据与数据集关联的标签大致平均地分为三部分。这确保了任何一个分区中都不会遗漏标签,从而避免了可能导致误信息的情况。以二分类为例,假设数据集被随机划分为训练集、验证集和测试集三个部分,且每个部分的大小是预先指定的。由于每行数据是随机地分配到这三部分中的一个,因此存在分区中可能只包含二分类标签中的一个标签的概率。由于验证集或测试集通常被分配较小的数据量,它们面临这种问题的潜在概率更大。假设模型错误地只训练来预测单一标签。如果该标签正好是只在验证集和测试集里存在的标签,机器学习的从业者可能会误以为模型表现极好,实际上,它是无用的。在进行完全随机划分时,你永远不知道什么时候会不走运,所以只要可能,尽量使用分层随机划分!
这里描述的数据划分策略只构建了一个模型,这个模型将在模型部署的推理模式下使用。当最终模型部署的推理运行时长是一个关注点时,这种策略是标准选择,尤其是在需要更快的模型,而不是稍微提高精度指标时。如果可以接受牺牲一些精度性能来换取运行时长,那么可以使用一种叫做k 折交叉验证集成的替代策略。这是许多机器学习竞赛中广泛推荐的方法,尤其是在 Kaggle 上举办的竞赛中。该方法使用之前描述的k折交叉验证,但实际上使用在交叉验证过程中训练的k个模型,并对这k个模型的预测结果进行集成。一种叫做混合的方法将模型的预测结果聚合,几乎总是能提高单个模型的精度指标。这个过程可以被看作是一种利用每个k模型的最佳思路和专业知识的方法,从而使最终结果作为一个整体更好。这个整体可以是k个预测的平均值或中位数。
在进入下一个方法之前,有一个小贴士:在比较不同实验中的模型时,始终记得确保数据划分一致。误信息通常发生在当两个模型在不同的数据划分策略和数据划分下分别开发时。即使其中一个模型在性能上明显优于另一个,也并不意味着什么,这种比较没有任何意义。
接下来,我们将深入探讨不同数据模态的数据表示组件。
为训练深度学习模型表示不同的数据模态
到目前为止,我们已简要讨论了数值、分类、文本、音频、图像和视频模式的利用。这些是跨多个行业最常用的模式。表示不同数据模式是一个复杂的话题,因为除了常见模式外,实际上还有很多稀有的数据模式。例如,稀有模式包括化学公式(文本数据的特殊结构形式)、文档数据(另一种具有复杂位置信息的文本数据的特殊形式)和图数据。在本节中,我们仅讨论常见的非结构化模式的表示方式,以确保内容与读者的相关性。数值和分类数据被认为是结构化数据,已经在前面的章节中得到了充分的讨论。现在让我们开始讨论文本数据模式。
监督式深度学习的文本数据表示
文本数据的表示方式多年来得到了极大的改进。以下是一些相关方法的列表:
- 词频-逆文档频率(TF-IDF)与 N-gram:这里的术语是通过 N-gram 实现的。N-gram 是相邻的n个文本字符序列。N-gram 是通过一种叫做分词的方法生成的。分词可以是像单个字符这样低级的表示,也可以是更高级的表示,比如单词。一旦表示为 N-gram,就可以使用以下公式计算 TF-IDF。
TF-IDF = 词频 x 逆文档频率
词频只是单行的计数数组。逆文档频率通过以下公式计算:
IDF = log( 文本样本总数 ______________________________________ 包含该术语的文档数 )
这种表示法是一种高效且轻量级的从文本中提取有用信息的方式,其中稀有的词具有更高的值,而像“the”和“and”这样的常见词会被抑制。TF-IDF 的输出可以直接输入到一个简单的多层感知器(MLP)或任何机器学习模型中,生成预测模型。在简单的用例中,这种表示法足以达到良好的性能指标。然而,在需要解码文本和标签的复杂交互的更复杂的用例中,它可能表现不佳。
-
词/标记嵌入:词嵌入可以从头开始训练,也可以从更大的数据集上进行预训练。预训练的嵌入可以通过监督学习或无监督学习的方式在较大的数据集上进行预训练。然而,嵌入方法在训练、评估和测试阶段存在标记不匹配的问题。这意味着在查找嵌入表之前,必须对特定文本标记的预处理方式进行大量调整。这个现象在评估和测试阶段被称为词汇外(OOV)。在训练阶段,同一个单词的不同变体会有各自的含义,这在学习和资源空间利用上是低效的。实际上,像词干提取、词形还原、转小写以及已知的词对词替换等方法通常会被用来缓解 OOV 问题,但问题不会完全解决。这些词嵌入可以与递归神经网络(RNNs)或变换器模型结合使用。
-
基于子词的分词法:这一类方法试图解决标记不匹配问题以及标记词汇表大小过大的问题。子词这个词可能听起来不直观,因为我们人类使用完整的词汇来感知文本的意义。这些算法只有在无法识别单词或单词被认为是稀有时,才会执行子词分词。对于常见单词,它们将保持为完整的词标记。这类方法的例子包括字节对编码(BPE)、WordPiece和SentencePiece。我们将简要介绍这些方法,作为简单的指南:
-
BPE 分词法:BPE 将文本视为字符,并在训练过程中迭代地将最常见的连续字符分组。迭代的次数决定了在训练过程中,何时停止将最常见的字符分组。此形式化方法允许稀有单词保持为子词标记,而常见单词则可以合并为一个单独的标记。这种表示方法特别用于生成预训练变换器(GPT)模型。然而,这种表示方法存在一个问题,就是同一个单词可能有多种编码方式。
-
WordPiece:WordPiece 在 BPE 的基础上进行了改进,通过使用语言模型来选择最可能的标记对进行分组。这在决定如何编码特定单词时,提供了一种智能选择方式。该算法被双向编码器表示的变换器(BERT)和高效学习分类标记替换的编码器(ELECTRA)所使用。
-
SentencePiece:SentencePiece 是一种优化基础分词器(如 BPE)生成的标记的方法。它使用几个组件,例如使用一种形式的 Unicode 文本转换来确保不存在依赖语言的逻辑,并使用称为子词正则化的方法,执行一种子词标记组增强形式(从预测的子词标记的前 k 个中以概率和随机方式选择单个样本来组合使用语言模型)。这个算法是 XLNet 和A Lite BERT(ALBERT)最显著使用的。
-
文本数据被表示为 DL 的标记。这也意味着标记数量会有严格限制,以便 NN 模型能够以正确的参数初始化。根据需要获得良好的模型性能,选择一个合理的标记大小限制。由于大小限制会影响模型大小,请确保模型大小不会过大,超过推理运行时要求。
关于缺失文本数据,在多模态数据的实际使用情况中可能会出现,使用空字符串作为插补方法是最自然的方式。在底层,这些模型通常会使用全零表示文本数组。
现在我们简要介绍了监督文本数据表示,让我们接下来探索监督音频数据表示。
表示 DL 的音频数据
音频数据是带有一个或两个值数组的时间序列数据,其中数组中的每个值表示特定时间戳处的单个音频数据片段。音频数据可以表示为从原始原始数据简单归一化形式表示的东西,表示为称为声谱图的二维数据,或作为Mel 频率倒谱系数(MFCCs)。
声谱图是一个称为wav2vec 2.0的过程的结果,这是一种变压器类型。
STFT 过程具有可以影响结果表示的超参数。以下列表总结了这些超参数以及如何正确设置它的技巧:
-
采样率:这指定了在应用 STFT 之前将使用的每秒样本数(赫兹/Hz)参数。音频数据可能以不同的采样率录制,并且为了构建模型,这些数据将需要通过重采样算法统一到单一的采样率。最常用的值是 16,000 Hz。由于这个值会影响模型在给定固定时间窗口下处理的大小和运行时间,请确保仅在指标表现需要时才增加它。
-
STFT 窗口长度:每个窗口大小将负责特定时长和特定位置的数据。每个窗口将在同一时间窗口内为一系列频率生成一个单一的值。这个参数的典型值为 4096 或 2048。根据你的使用案例中对预测分辨率的要求来配置这个值,如果有严格的需求的话。这个参数还会影响模型的大小。
-
窗口步幅:这与卷积层的滤波器步幅类似,并且没有太多显著的调节方法。使用窗口长度的一个小百分比,比如 10%,应该是一个足够好的设置。
-
是否使用 Mel 频率尺度:Mel 频率尺度是音频信号频率的对数变换。从根本上讲,它是一种模仿人类如何感知音频的变换。它使高频变化的影响变小,低频变化的影响变大。当涉及到某种形式的人工判断以提高度量性能时,应使用这种变换。
对于空的音频行,可以使用预生成的单个随机噪音音频或使用与之长度相同的零数组来填充,这在多模态数据集中通常效果不错。
现在我们简要介绍了有监督的音频数据表示,接下来让我们探索有监督的图像和视频数据表示。
表示图像和视频数据以供深度学习使用
图像数据在这里不需要太多介绍,因为我们已经通过一些教程直接使用了它们。关键是要在将数据输入神经网络模型(如 CNN)之前进行某种归一化,神经网络将提取出很好的特征表示。变压器(Transformers)在图像任务中也与卷积神经网络(CNN)展开了激烈的竞争,并且可以用来提取代表性特征并直接进行任务预测。需要注意的一点是,除非图像的分辨率对于识别某些模式至关重要,否则通常使用较小的图像分辨率作为模型会更有效。尽管图像分辨率较小时,人工可能无法直观看到某些模式,但计算机仍然可以识别。图像分辨率会影响训练的运行时间、生产环境中模型的运行时间,有时也会影响模型的大小,特别是对于变压器模型,因此确保合理配置。
然而,视频数据是图像数据的一种扩展形式,多个图像按顺序排列形成一个视频。这意味着视频数据是一种序列数据,就像文本一样,缺乏绝对的时间戳信息。每个连续的图像被称为帧。视频可以有多种帧率,通常为 24、30 或 48 每秒帧数(FPS),但一般可以是任何数字。对于计算机视觉(CV)的使用案例,确保设置较低的 FPS,以便根据具体的使用场景减少处理负担。例如,唇读的使用案例需要的 FPS 比让模型判断一个人是否在跑步的需求要低。对于帧的分辨率,图像分辨率的相同指南也适用。一旦决定了视频属性,就必须学习并提取代表性特征。当前的最先进(SoTA)特征是通过类似于基于图像的使用案例的模型提取的。这样的模型包括 3D 卷积神经网络(CNN)和 3D 变压器(transformers)。
然而,这两种数据类型之间是有交集的,那就是通过视频数据提取的图像。对于这种类型的图像数据,可以减少预测模型做出错误预测的概率。机器学习模型并不是完美的预测器,因此,每当有机会减少错误预测(如假阳性或假阴性)而不影响真实预测时,应该考虑采取这一机会。可以考虑使用来自 OpenCV 库的手动图像处理技术,在模型将图像作为输入之前执行任何初步步骤。例如,可以使用 OpenCV 中的运动检测技术作为喂给深度学习模型视频数组之前的初步条件检查器。由于大多数视频数据的使用案例需要运动,所以如果没有任何运动,预测任何东西就没有意义。这也减少了在多个未更改的视频帧上进行预测时可能出现的错误预测。OpenCV 中的运动检测器通过简单的像素值变化来工作,而不使用概率模型,因此它是更可靠的运动指示器。
现在我们已经涵盖了不同数据模态的表示,接下来我们讨论数据增强的话题。
增强数据以训练更好的深度学习模型
数据增强在深度学习中被广泛使用,以提高训练模型的泛化能力并提升模型的度量性能。通过考虑增强带来的额外独特变异,模型将在验证、测试和推理阶段能够关注外部数据中的这些独特变异。自然,这也会减少模型对特定模式的过度依赖,并且消除足够大小数据集带来的好处。数据增强增加了训练数据的量,从而增加了训练数据中模式的变异性。这个过程通常在每次训练迭代时随机且独立地在内存中完成。这样可以确保训练数据的额外变异性没有限制,并且不需要额外的存储空间。然而,你不能仅仅随便使用所有已知的特定模态的数据增强类型。以下是你可以对数据进行的不同类型的数据增强:
-
图像:通过对比度限制自适应直方图均衡(CLAHE)锐化图像,色相和饱和度变化,颜色通道打乱,对比度变化,亮度变化,水平/垂直翻转,灰度转换,模糊,图像遮罩,mixup(图像及其标签的加权组合),cutmix(mixup,但仅通过原始图像的随机补丁),等等。更多的增强方法可以查看
github.com/albumentations-team/albumentations,其中有超过 70 种增强方法! -
文本:同义词替换,反向翻译(将文本翻译成另一种语言后,再翻译回原语言),等等。
-
视频:视频 mixup(与图像 mixup 相同,但适用于视频),以及与图像相同的所有增强方法。
-
librosa库。
选择使用何种增强类型需要对你在部署模型时将遇到的预期环境有一定的了解。错误的选择会为模型添加噪声,并可能在训练过程中让模型产生困惑,从而导致度量性能下降。好的选择则围绕估计在实际环境中可能出现的变异。举一个制造业应用的例子,目标是部署一个基于图像的模型,用于通过摄像头传感器预测生产线上的产品特征以进行分拣。如果假设摄像头在所有设置下几乎完美地固定直对机器,使用图像旋转增强可能就不太明智。即便你想使用该增强,旋转变化的范围也应仅限于较低的范围,比如小于 10 度的变化。如果摄像头不提供灰度图像,那么使用灰度增强也显得不太直观。
这标志着训练有效模型的数据准备阶段的结束。接下来,我们将深入探讨工作流程中的模型训练阶段。
配置和调优深度学习超参数
超参数配置和调优在有效训练深度学习(DL)模型中起着至关重要的作用。它们控制着模型的学习过程,并能显著影响模型的性能、泛化能力和收敛性。本节将讨论一些重要的超参数及其对训练深度学习模型的影响。
训练每个神经网络(NN)模型时需要配置的最常见且有影响力的超参数是其训练轮次、早停轮次和学习率。这三个参数被视为一组参数,合在一起构成了学习计划。已有一些著名的学习计划,旨在以最少的时间获得最佳模型。然而,这些方法很大程度上依赖于对模型可以收敛的总轮次数的初步估计。依赖于对总轮次数估计的方法较为脆弱,其配置策略也不容易从一个问题迁移到另一个问题。在这里,我们将重点使用验证数据集来跟踪实现最佳度量性能所需的轮次数。
早停轮次是一个控制你希望在停止之前继续训练多少轮的参数。该策略意味着,训练轮次的超参数可以设置为无限大或非常大的数字,以便找到在验证数据集上表现最好的模型。早停通过基于验证数据集动态减少所需的训练轮次。当模型提前停止时,通过保存验证数据集上表现最好的模型权重,你将能够加载这些最佳表现的权重。典型的早停轮次为10。
至于学习率,实际上有两个普遍有效的方向。一种方法是立即使用较大的学习率,比如0.1,并逐渐衰减学习率。学习率的逐渐衰减可以通过监控验证得分来实现,当验证得分在3到5轮后没有改善时,就减少一定比例的学习率。第二种方法是在使用第一种方法之前,先用较小的学习率作为暖身方法初始化神经网络(NN)的基本权重。在学习的初期,由于模型处于随机状态,学习过程可能非常不稳定,损失值似乎没有按照合理的改进趋势变化。使用暖身可以帮助初始化稳定的基础,从而使损失值稳定下降。需要注意的是,如果使用预训练权重来初始化模型,通常不需要暖身,因为模型已经处于稳定状态,特别是当预训练权重来自相似数据集时。
批量大小是深度学习模型训练中的另一个关键超参数,因为它决定了在优化过程中每次更新模型权重时使用的训练样本数量。批量大小的选择会显著影响模型的训练速度、内存需求和收敛性。较小的批量大小,如 16 或 32,能提供更准确的梯度估计,从而实现更稳定的收敛,但可能需要更多的训练迭代,并且由于计算并行性较低,训练速度较慢。另一方面,较大的批量大小,如 128 或 256,增加了并行性,能够加速训练过程并减少内存需求,但可能导致梯度估计不够准确,进而可能影响收敛的稳定性。实际上,实验不同的批量大小是非常重要的,以找到在训练速度和收敛稳定性之间提供最佳平衡的批量大小,针对你的具体问题。此外,现代深度学习框架通常支持自适应批量大小技术,可以在训练过程中自动调整批量大小,以优化学习过程。
这里讨论的基础策略是稳健的,大多数情况下能够轻松获得表现最佳的模型,尽管牺牲了一些额外的训练时间。值得注意的是,正则化方法、优化器和不同的激活函数已在第二章,深度学习架构设计中讲解过,我鼓励你参考该章节以获取更多关于这些主题的信息。
尽管配置这些超参数可以有手动策略,但总会有进一步调优超参数的空间,以优化模型的指标表现。通常,调优可以通过网格搜索、随机搜索或更智能的搜索机制来执行。网格搜索,也称为暴力搜索,探索并验证所有指定超参数值的可能组合,通过交叉验证确定给定问题的最优配置。对于更智能的调优方法,请回到第七章,深度神经架构搜索,获取更多的见解。此外,模型评估指标在这个超参数调优过程中起到重要作用,我们将在第十章,探索模型评估方法中进一步探讨。
超参数调优的核心在于过程和工作流,能够迭代地执行、可视化、跟踪并比较建模实验,每个配置都是建模实验的一部分。这引出了下一个主题——有效训练深度学习模型的实际工作流。
执行、可视化、跟踪并比较实验
高效执行 ML 项目的关键是快速在实验之间迭代。任何 ML 项目都需要大量的探索,无论是在项目的初期阶段,以评估用例的可行性,还是在后期阶段,以提高模型的性能。当这个探索过程能够得到优化时,本应失败的事情可以迅速失败,而有前景的事情可以迅速成功。在实际的 ML 项目中,失败是非常常见的。一旦我们承认这一点并快速失败,我们就可以利用恢复的时间来解决更有价值的用例。一个好的 MLOps 平台将帮助我们高效、有效地执行、可视化、追踪和比较实验。
让我们通过一个实际的例子来演示,使用 Iris 数据集和 MLP 模型,并使用 MLflow MLOps 平台。我们还将使用catalyst库,它也被认为是一个 MLOps 平台,尽管它只是部分地并主要集中于提供常见的pytorch深度学习模型训练工具。由于catalyst提供了大多数的模型版本控制和模型存储机制,我们将只利用 MLflow 中的追踪功能。这个例子的步骤如下:
-
首先,让我们导入所有必要的库:
import json import osimport numpy as np import torch import torch.nn as nn import torch.nn.functional as F from catalyst import dl, utils from catalyst.contrib.datasets import MNIST from sklearn import datasets from sklearn.metrics import log_loss from sklearn.model_selection import train_test_split from sklearn.preprocessing import MinMaxScaler from torch import nn as nn from torch import optim from torch.utils.data import DataLoader from torch.utils.data import TensorDataset from catalyst.loggers.mlflow import MLflowLogger -
接下来,由于我们将使用基于
pytorch的 MLP,我们将在pytorch中再次设置随机种子:torch.manual_seed(0) -
我们将在这里使用的实际实现数据集是 Iris 数据集。该数据集包含了不同种类的花卉的花瓣和萼片长度,以及三种不同的鸢尾花类型。现在让我们加载这个数据集:
iris = datasets.load_iris() iris_input_dataset = iris['data'] target = torch.from_numpy(iris['target']) -
缩放是一种正则化方法,可以减少记忆并减少偏差。让我们在这里执行简单的最小值和最大值缩放:
scaler = MinMaxScaler() scaler.fit(iris_input_dataset) iris_input_dataset = torch.from_numpy(scaler.transform(iris_input_dataset)).float() -
要训练一个模型,我们需要一个合适的交叉验证策略来验证模型的有效性和性能。我们将使用 77%的数据用于训练,33%的数据用于验证。让我们为交叉验证准备数据:
X_train, X_test, y_train, y_test = train_test_split(iris_input_dataset, target, test_size=0.33, random_state=42) -
接下来,我们需要使用准备好的
numpy格式数据为交叉验证准备数据加载器:training_dataset = TensorDataset(X_train, y_train) validation_dataset = TensorDataset(X_test, y_test) train_loader = DataLoader(training_dataset, batch_size=10, num_workers=1) valid_loader = DataLoader(validation_dataset, batch_size=10, num_workers=1) loaders = {"train": train_loader, "valid": valid_loader} -
由于这是一个三类的多分类问题,我们将使用
pytorch中的交叉熵损失函数:criterion = nn.CrossEntropyLoss() -
我们将在这个项目中使用名为
catalyst的pytorch高级封装库。在catalyst中训练一个模型时,我们必须定义一个名为 runner 的模型训练器类实例:runner = dl.SupervisedRunner( input_key="features", output_key="logits", target_key="targets", loss_key="loss" ) -
我们将在本项目中使用 MLP,并使用一个 MLP 构造类,它允许我们指定输入数据大小、隐藏层配置和输出数据大小。隐藏层配置是一个层大小的列表,它同时指定了层数和每一层的大小。假设我们想随机获得 20 种不同的隐藏层配置,并找出哪种配置在验证集上表现最好。首先,让我们定义一个方法,随机生成配置:
def get_random_configurations( number_of_configurations, rng ): layer_configurations = [] for _ in range(number_of_configurations): layer_configuration = [] number_of_hidden_layers = rng.randint(low=1, high=6) for _ in range(number_of_hidden_layers): layer_configuration.append(rng.randint(low=2, high=100)) layer_configurations.append( layer_configuration ) layer_configurations = np.array( layer_configurations ) return layer_configurations rng = np.random.RandomState(1234) number_of_configurations = 20 layer_configurations = get_random_configurations( number_of_configurations, rng ) -
现在,让我们定义一个方法,允许我们训练和评估不同的 MLP 配置:
def train_and_evaluate_mlp( trial_number, layer_configuration, epochs, ):此处的试验编号清晰地区分了不同的实验。除了层配置外,我们还可以配置要运行的训练周期数。在这种方法中,我们将基于传入的层配置创建一个 MLP 模型实例:
model = MLP( input_layer_size=iris_input_dataset.shape[1], layer_configuration=layer_configuration, output_layer_size=len(np.unique(target)), ) -
我们将使用
Adam优化器进行梯度下降,并设置检查点目录:optimizer = optim.Adam(model.parameters(), lr=0.02) checkpoint_logdir = "experiments" -
接下来,我们将在
catalyst中定义一个 MLflow 日志记录助手类,用于以 MLflow 格式记录实验数据。在这个设置中,我们记录训练和验证日志损失的均值和标准差:loggers = { "mlflow": MLflowLogger( experiment="test_exp", run="test_run" ) } -
最后,我们将开始训练过程,该过程将训练指定数量的训练周期:
runner.train( model=model, hparams=hparams, criterion=criterion, optimizer=optimizer, loaders=loaders, num_epochs=epochs, callbacks=[ dl.CheckpointCallback( logdir=checkpoint_logdir, #save_n_best=0, loader_key="valid", metric_key="loss", mode="model", ) ], logdir="./logs", valid_loader="valid", valid_metric="loss", minimize_valid_metric=True, verbose=verbose, loggers=loggers ) -
作为最后的代码步骤,我们将循环遍历每个随机生成的层配置,并执行训练和评估过程:
for layer_config in layer_configurations: train_and_evaluate_mlp( trial_number, layer_config, epochs=10, load_on_stage_start=False ) -
现在,我们需要启动 MLflow 服务器服务。我们可以通过在与包含引入代码的目录相同的目录中运行以下命令来完成此操作:
mlflow server –backend-store-uri mlruns运行此命令后,当前目录应该包含一个名为
.catalyst的文件,该文件指示catalyst启用 MLflow 支持。该文件应包含以下内容:[catalyst] cv_required = false mlflow_required = true ml_required = true neptune_required = false optuna_required = false一旦命令执行并且通过打开 HTTP 网站链接,你应该能够看到 MLflow 的界面,如图 8.4所示:

图 8.4 – MLflow 界面
该界面展示了一个方便的方式来可视化不同实验之间的性能差异,同时展示所使用的参数。数值度量值可以排序,从而获得在验证分区上表现最好的模型,如图所示。准备数据和训练模型的过程需要在不同设置或实验之间进行迭代比较。通过定量度量和实验参数,可以更客观地比较实验。当通过代码在界面中自动可视化展示,而不是手动插入到 Excel 或 Google 表格时,这使得过程更加可靠和有序。此外,如果你点击任何一个实验,你将能够交互式查看每个训练周期的损失曲线,如图 8.5所示:

图 8.5 – MLflow 界面,显示最佳模型的示例损失曲线
在确保迭代速度的同时,还需要正确组织和跟踪你为模型生成的所有工件。这意味着你需要为你的模型、数据集以及任何影响最终模型输出的关键组件进行版本控制。工件可以是模型权重、指标性能报告、性能图、嵌入可视化和损失可视化图。显然,这项任务可以通过手动编码来完成。然而,当实验数量增加时,组织模型内置实验的工件会变得杂乱无章。任何自定义文件、图表和指标都可以绑定到每个实验记录中,并在 MLflow 界面中查看。这里的实验可以通过使用不同的模型、不同的数据集、不同的特征化方法、模型的不同超参数,或相同数据集的不同样本大小来区分。此外,模型还可以直接存储在 MLflow 的模型注册表中,这使得 MLflow 可以直接部署该模型。
探索模型构建技巧
这些实际内容作为一个例子,展示了像 MLflow 这样的 MLOps 平台如何简化构建和选择正确模型的过程,既可以通过编程也可以通过可视化实现。尽管 MLOps 非常优秀并且有助于高效训练模型,但仍有一些事情是 MLOps 平台无法为你处理的,而这些事情在模型被正确利用并且预测结果被消费之前是关键的组成部分。以下是这些组件:
-
预测一致性验证测试:这是一个确保同一训练模型在相同数据上做出的预测是一致的测试。如果模型的预测逻辑不是确定性的,那么就不能利用该模型的预测。这个问题将在第十章,“探索模型 评估方法”中进一步讨论。
-
pytorch,这可以通过以下代码全局实现:import torch import random import numpy as np torch.manual_seed(0) random.seed(seed) np.random.seed(seed)在
tensorflow和keras中,这可以通过以下代码全局实现:import tensorflow as tf tf.keras.utils.set_random_seed(seed)该方法会自动设置
random和numpy库的种子。这些全局设置可以帮助为那些没有明确设置随机数生成器种子的层设置随机种子。
实验中的最后一条建议是确保在项目开始时创建基准线。基准线是可能的解决方案的最简单版本。这个解决方案甚至可以是一个非深度学习模型,具有简单的特征。拥有基准线可以帮助确保你添加的任何改进或复杂性都是通过指标性能监控来证明其合理性的。避免为了复杂而复杂。记住,机器学习项目的价值不在于过程的复杂性,而在于能够从中提取的结果。
接下来,我们将深入探讨可以用于实现和改进利用深度学习的解决方案的实际技术。
探索实现和改进监督式深度学习解决方案的通用技术
请注意,在本章前面,我们专注于基于问题类型的使用场景,而不是问题本身。解决方案则是解决并处理这些问题。深度学习和机器学习在解决人员配置困难和自动化日常任务方面非常有效。此外,计算机中的机器学习模型可以比普通人更快地处理数据,从而实现更快的响应时间和更高效的过程扩展。在许多情况下,机器学习模型能够提高过程的准确性和效率。有时,它们改善当前的流程,其他时候,它们使得过去无法实现的流程变为可能。然而,单一的深度学习模型可能不足以解决问题。让我们来看一个可以通过单一深度学习模型充分解决的例子。
考虑使用深度学习模型来预测婴儿性别的使用场景,模型通过超声波图像来进行预测。传统上,医生会基于视觉对母亲子宫中婴儿的超声波图像进行性别分析,既有实时的分析,也有离线的分析,最后给出性别预测。根据以往的经验和知识,医生在解读性别时会有不同的能力和准确性。当婴儿出现异常情况时,事情可能变得更加复杂。潜在的根本问题是,经验丰富且能力强的医生稀缺且聘用成本高。如果我们有一个系统可以自动从超声波图像中解码性别,这将能为医生的判断提供很好的辅助,或者作为一种更便宜的替代品,取而代之。同样的类比也可以应用于识别任何高级影像结果中的疾病或症状,例如 X 光图像。
这个例子将一个深度学习模型描绘为解决方案的一个组成部分,并展示了在某些情况下,单一的深度学习模型足以获得期望的输出。这是一个关于人员配置问题的例子,但在效率方面的讨论较少。请注意,对于某些使用场景,可能需要以某种形式解释驱动预测的原因。换句话说,你需要解释模型做出决策的理由。为了帮助医生,指出哪些模式及其类型对决策产生了影响,比单纯给出决策本身更有帮助,因为医生可以利用这些额外的信息做出自己的判断。这将在第十一章,神经网络预测的解释 中详细介绍。
除了解释之外,并非所有问题的解决方案都能仅通过单一的机器学习(ML)或深度学习(DL)模型来完成。有时候,深度学习模型需要与一般的机器学习方法结合使用,而在其他情况下,需要将多个深度学习模型组合在一起。在某些特殊情况下,必须对中间数据进行特殊处理和准备,然后再将其输入到管道中的下一个任务。在处理此类问题时,创建和构建逻辑管道是至关重要的。让我们以一个需要多个数据集、多个深度学习模型以及构建任务管道的例子来说明问题和解决方案。
假设有一个问题:找到刚刚抢劫银行的罪犯。通过使用部署在城市中的 CCTV 摄像头,如果你已经识别出抢劫犯的面孔,可以使用人脸检测和识别解决方案。下图展示了该问题的解决方案任务管道示例:

图 8.6 – 通过 CCTV 摄像头寻找罪犯的任务管道
人脸检测是一个图像物体检测过程,其中包括一个图像边界框回归器和一个二分类器,用于预测该边界框是否为人脸。代表性的人脸特征提取利用深度学习模型,可以使用监督式表示学习方法进行训练,目的是优化人脸特征在不同个体间的区分效果。接下来,需要一个单独的任务来构建罪犯人脸特征的数据库,并将其传递给 KNN 机器学习算法,以便根据从城市 CCTV 摄像头获得的查询人脸特征,找到匹配的人脸 ID。这个解决方案展示了将一个解决方案分解为多个组件的必要性,以最终实现寻找罪犯的目标。
上述示例是更大范式的一部分,称为多任务学习和多任务问题。多任务范式是一系列主题,它不仅能够推动机器学习领域的更大发展,尤其是在深度学习中,由于其固有的灵活性,这一切变得更加可实现。在下一个主题中,我们将深入探讨多任务范式。
在监督式深度学习中分解多任务范式
多任务是一种涵盖广泛任务的范式,它涉及在多个问题上执行机器学习模型,并将相应的数据集与之结合,以实现目标。这个范式通常基于以下两个原因构建:
-
为了获得更好的预测性能和泛化能力。
-
将复杂的目标分解为可以通过单独的机器学习模型直接解决的小任务。这重申了前一个主题中的观点。
让我们深入探讨四种多任务技术,从多任务管道开始。
多任务管道
这种多任务系统的变种围绕着实现那些无法通过单一 ML 模型直接解决的方案。将高度复杂的任务分解成较小的任务,可以通过多个 ML 模型处理不同的小任务来提供解决方案。这些任务可以是顺序的或并行的,并且通常形成一个有向无环图(DAG)样的管道,类似于图 8.6中所示的例子。
然而,这并不意味着任务必须仅限于机器学习(ML)模型。不同产业和业务的问题可以有多种形式,灵活地分配所需组件以产生解决方案是从 ML 技术中提取价值的关键。例如,如果在分解大任务后需要人工监督来完成某个任务,不要犹豫,利用人工监督与 ML 模型共同作用来实现价值。让我们通过另一个使用多任务管道来创建解决方案的用例——推荐系统,来进一步了解。首先,我们需要进行有监督或无监督的表示学习以提取特征。其次,利用提取的特征,创建一个用于匹配查询特征的数据库。第三,从数据库中获取排名前 k 的最接近数据,并应用回归模型来预测前 k 数据的排名,以进行高效的高性能排名微调。
接下来,我们将探索另一种多任务范式,称为迁移学习(TL)。
迁移学习(TL)
迁移学习(TL)是一种技术,涉及将从一个任务中学到的知识应用到另一个任务中。其核心原因可以是以下之一:
-
提高度量性能。
-
减少网络达到收敛状态所需的 epoch 数量。
-
允许更稳定的学习轨迹。在其他情况下,当初始学习过程不稳定时,网络可能需要更长的时间才能收敛。然而,在一些情况下,当网络没有稳定的基础开始学习时,网络根本无法收敛。迁移学习可以帮助那些最初无法学习的模型在学习过程中达到收敛。
-
提高泛化能力并减少过拟合的概率。当第二个任务仅涉及来自实际数据集的一小部分变化时,从第一个任务中学到的知识(该任务涵盖了更广泛的变化范围)有助于防止狭隘的忽视。
具体来说,DL 中的 TL 是通过在第二个任务中使用从第一个任务中学到的网络参数来实现的。这些参数涉及与网络相关的所有权重和偏置。可以将这些参数作为初始化步骤,用于同一个网络,而不是使用通常的随机初始化参数。这些被称为预训练权重。使用预训练权重进行网络学习的过程称为微调。此外,网络参数也可以选择完全冻结,并仅作为一个特征提取器组件,为另一个监督学习算法提供特征。
有几种自动化策略,专注于提高微调后的结果。然而,这些方法并非万能,执行起来可能需要大量时间。通过 TL 实现更好性能的实际策略是,通过评估两个任务的迁移能力来选择要训练的层数。表 8.2展示了根据任务相似性和第二个任务的数据集大小,决定 TL 策略的简易方法:
| 数据集大小 | |
|---|---|
| 小 | 大 |
| 任务相似性 | 低 |
| 高 | 冻结所有基础网络参数,增加一个额外的线性预测层,只在数据集上训练该线性层。 |
表 8.2 – 深度 TL(DTL)策略指南
为了清晰起见,假设“大的”数据集是指数据集至少包含 10,000 个样本。对于任务的迁移能力/相似性,需要依靠人类直觉来进行逐个案例的评估。在本指南中,我们假设大的数据集意味着数据集具有足够的变化,能够充分代表总体。然而,在前面的图中没有展示的一个隐藏因素是第一个任务的数据集大小。当任务相似性较高、第二个任务的数据集较小、并且第一个任务的数据集较大时,TL 会产生最佳效果。第一个数据集的大小通常也限制了可以使用的神经网络的规模。假设第一个数据集的大小较小;在这种情况下,表现最佳的通常是较小的模型。当 TL 非常有益时,即使第二个数据集的数据集大小为中等或较大,较小的模型仍然可以超越较大的模型。在这种复杂情况下,需要进行平衡才能获得理想的模型。
TL 中的一个显著问题被称为灾难性遗忘。这是一个现象,当网络在新的任务上训练时,其在早期任务上的性能会退步。如果你不关心之前任务的性能,这个问题可以忽略不计。实际上,如果需要保持之前任务的性能,你可以按照以下步骤操作:
-
使用统一的指标,通过额外在第一个任务的验证数据集上进行验证,来处理第一个任务和第二个任务的性能。
-
将第一个任务和第二个任务的数据集结合起来,并作为单一模型进行训练。如果这些目标彼此不相关,则使用不同的全连接层预测头。
最后,还有一种在迁移学习(TL)中流行的技术,称为知识蒸馏。该方法涉及两个模型,其中一个预训练的教师模型用于将其知识蒸馏到学生模型中。通常,教师模型是一个较大的模型,能够学习更准确的信息,但运行速度较慢,而学生模型是一个较小的模型,在运行时能以合理的速度运行。该方法通过使用额外的基于相似性的损失来蒸馏知识,这通常是教师模型和学生模型之间选定层输出的损失,通常是对数层,在基础交叉熵损失的基础上进行计算。该方法鼓励学生模型生成与教师模型相似的特征。通常,这项技术用于获得一个比没有知识蒸馏时训练出的更小的、精度更高的模型,从而使部署基础设施可以更为经济。这项技术将在第十三章《探索偏见与公平》中作为关键技术进行实际介绍,以减轻偏见。
接下来,我们将深入探讨另一种多任务执行方式,称为多目标学习。
多目标学习
多目标学习是一种多任务处理过程,涉及同时训练多个不同的目标。不同的目标引导网络的学习轨迹走向不同的路径。多目标学习可以进一步细分为以下几种选项:
-
在相同输出上进行多重损失计算。
-
多个目标,由独立的神经网络预测头处理,每个预测头都有其各自的损失。这可以进一步细分为以下几类:
-
具有实际影响和应用的多个目标。
-
一个或多个主要目标和一个或多个辅助目标。辅助目标与各自的损失配对,这些损失被称为辅助损失。
-
除了选项 2(A),其他选项(即 1 和 2(B))主要用于提高指标性能。指标可以像准确率或对数损失这样简单,或更复杂,例如偏向少数类别的程度。一个更简单、直接的多目标学习示例是多标签目标类型。多标签是指一个数据行可以关联多个标签。这意味着设置将是多个二分类目标,这正是 2(A) 的情况。
多个目标及其相关的损失意味着在学习过程中可能会出现梯度冲突问题。这种现象通常被称为负迁移。负迁移的一个极端情况是,当两个损失的梯度大小相同且方向完全相反时,它们会相互抵消。这会阻碍模型的学习过程,使模型永远无法收敛。实际上,这个问题可能表现为较低的规模,并减缓收敛速度,或者更糟糕的是,可能引入巨大的波动,导致模型无法学习到任何东西。不幸的是,除了理解模型学习不佳的背景原因外,没有什么“灵丹妙药”的缓解方法。通常需要反复实验,找出如何平衡这些损失,以鼓励稳定的学习过程。
接下来,我们将深入探讨多模态神经网络的训练。
多模态神经网络训练
多模态神经网络是一种多任务系统,因为负责不同模态的网络在同一任务中以完全不同的路径进行学习。处理多模态神经网络的常见方法是在初始阶段为不同的数据模态分配不同的神经网络块。神经网络块包含每个模态特定的网络。不同模态的神经网络块随后会通过一系列中间全连接层和输出全连接层进行合并。如图 8.7所示:

图 8.7 – 典型的多模态神经网络结构
利用多模态的想法是,额外的数据输入可以帮助识别更全面的模式,从而应该提高整体指标的表现。实际上,如果没有仔细处理训练过程,这种情况通常不会发生。不同的模态存在于完全不同的分布中,并且以不同的速度和不同的路径进行学习。对所有数据模态应用单一的全局优化策略可能会产生次优的结果。一种常见且有效的策略是:
-
预训练单独的模态神经网络块(单模态)并使用临时预测输出层,直到达到一定程度的收敛
-
移除临时预测输出层,并像往常一样使用单模态训练过程中的预训练权重来训练多模态神经网络(NN)
除此之外,冻结单模态训练神经网络的权重,只训练多模态聚合的全连接层预测头也是一种有效策略。还有许多更复杂的策略来解决这个问题,但它们超出了本书的范围。
总结
在本章中,我们探讨了有监督的深度学习,包括它可以解决的问题类型和实现及训练深度学习模型的技术。有监督的深度学习涉及在标记数据上训练模型,以对新数据进行预测。我们还介绍了多种有监督学习的应用案例,涵盖了不同问题类型,包括二分类、多分类、回归、以及多任务学习和表示学习。本章还涵盖了有效训练深度学习模型的技术,包括正则化和超参数调优,并提供了使用流行的深度学习框架在 Python 编程语言中的实际实现。
有监督的深度学习可以广泛应用于图像分类、自然语言处理(NLP)和语音识别等实际任务。通过本章提供的知识,你应该能够识别有监督学习的应用,并有效地训练深度学习模型。
在下一章中,我们将探讨深度学习中的无监督学习。
第九章:探索无监督深度学习
无监督学习适用于没有标签的数据。更广泛地说,无监督学习旨在揭示数据中隐藏的内在模式。监督机器学习项目中最严格和最昂贵的部分是为给定数据所需的标签。在现实世界中,有大量的无标签数据,其中包含可以学习到的丰富信息。坦率地说,获取世界上所有数据的标签是不可能的。无监督学习是解锁我们今天拥有的丰富无标签数字数据潜力的关键。让我们通过以下假设情境更好地理解这一点。
想象一下,获取一行数据的标签,无论其使用场景是什么,都需要花费 1 美元和 1 分钟,并且通过监督学习可以获得单个信息单元。要获取 10,000 个信息单元,需要花费 10,000 美元并贡献 10,000 分钟来获得 10,000 条标注数据。无论是时间还是金钱,都是令人痛苦的消耗。然而,对于无监督学习,通过相同的数据(没有标签),获取 0.01 个信息单元的成本是 0 美元和 0 分钟。由于数据量不受时间或金钱的限制,我们可以轻松地获取比 10,000 个样本多 100 倍的数据,并通过模型学习到相同的信息。当金钱和时间不再是问题时,假设无监督学习模型具备足够的容量和能力,你的模型可以学习到的知识量是无限的。
深度学习为无监督学习领域提供了竞争优势,因为它具有巨大的容量和学习复杂信息的能力。如果我们能够破解无监督深度学习的代码,它将为未来的模型铺平道路,这些模型有望接近通用智能!在本章中,我们将通过以下主题来探讨无监督深度学习的关键组成部分:
-
探索无监督深度学习应用
-
为下游任务创建预训练网络权重
-
通过无监督深度学习创建通用表示
-
探索零样本学习
-
探索无监督深度学习中的降维组件
-
检测外部数据中的异常
技术要求
本章包括一些Python编程语言中的实际实现。要完成本章内容,您需要一台安装了以下库的计算机:
-
pandas -
CLIP -
numpy -
pytorch -
pillow
您可以在 GitHub 上的github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_9找到本章的代码文件。
探索无监督深度学习应用
今天,实践者已经能够利用无监督深度学习挖掘其未标记数据,从而实现以下任一应用场景。这些应用按其影响力和实用性从高到低排列:
-
为下游任务创建预训练网络权重
-
创建可以直接用于下游监督任务的通用表示,这些表示可以被预测的监督模型直接使用
-
实现一-shot 学习和零-shot 学习
-
执行降维
-
检测外部数据中的异常
-
将提供的训练数据进行聚类分组
首先需要注意的是,纯聚类仍然是无监督学习的核心应用,但对深度学习而言并非如此。聚类是将未标记的数据分成多个任意的聚类或类别。这对于一些应用场景非常有用,例如针对性响应的客户细分,或者通过主题建模来发现人们在社交媒体上讨论的流行话题。在聚类中,利用未标记数据样本之间的关系来寻找彼此接近的数据群体。有些聚类技术通过假设每个聚类中数据呈球形分布来分组数据,例如K-means。其他一些聚类技术则更具适应性,能够找到多种分布和大小的聚类,例如HDBSCAN。
深度学习方法未能在传统非深度学习聚类方法如简单的k-均值算法或 HDBSCAN 算法上带来显著的改进。然而,已经有尝试通过利用聚类本身来辅助神经网络模型的无监督预训练。为了将其作为神经网络中的一个组件实现,聚类模型必须是可微的,以便梯度可以传递到整个网络。这些方法并不优于非深度学习技术,如k-均值或 HDBSCAN,只不过是聚类概念的一种变体,以便在神经网络中实现聚类。无监督预训练中的一个聚类应用实例是SwaV,将在下一节介绍。然而,为了完整性,传统上使用的一种基于神经网络的聚类算法是自组织映射(Self-Organizing Maps),但该网络本身并不被视为深度网络。
在接下来的几节中,我们将更全面地探讨另外五个应用,按其影响力和实用性排序,如前所示。
为下游任务创建预训练网络权重
这种方法也被称为无监督迁移学习,它类似于有监督迁移学习,并自然地获得了与第八章《探索有监督深度学习》中所描述的相同好处。但是,为了回顾一下,让我们通过一个类比来说明。假设你是一名厨师,已经花了多年时间学习如何烹饪各种菜肴,从意大利面和牛排到甜点。一天,你被要求做一道从未尝试过的新菜肴,我们称之为“菜肴 X”。你不是从零开始,而是利用已有的知识和经验来简化过程。你知道如何切菜,如何使用烤箱,如何调节火候,因此不需要重新学习这些步骤。你可以将精力集中在学习“菜肴 X”所需的特定食材和技巧上。这类似于迁移学习在机器学习中的应用,适用于无监督学习和有监督学习。已经在相关任务上训练过的模型可以作为起点,从而使得模型能够更快速、有效地学习新任务。
除了能够利用世界上无尽的未标注数据,无监督迁移学习还具有另一个好处。监督学习中的标签通常包含偏差,而模型会采纳这些偏差。通过学习获得的偏差可能会妨碍获取更通用的知识,而这些知识对下游任务可能更有用。除了标签中的偏差外,还有一些情况下标签本身就是错误的。无监督的含义是,模型不会从任何标签中学习到偏差或错误。然而,需要注意的是,在某些数据集中,偏差更为显著。具有质量相关标签(这些标签通常通过人工判断得出)的复杂任务数据集,相较于像判断图片是否有脸这样的简单任务,通常会包含更多的偏差。
现在,让我们深入探讨这些技术。无监督迁移学习被评为最具影响力和实用的应用之一,特别是在自然语言处理(NLP)领域的贡献。变压器模型(Transformers),在第六章《理解神经网络变压器》中介绍,为使用一种更常见的无监督学习技术——自监督学习预训练模型的范式铺平了道路。此方法的分类问题更多的是视角的问题,并不是所有人都会同意这一分类。自监督学习仅利用共现数据之间的关系,在没有标签的情况下预训练神经网络以获得关系性知识。从这个角度来看,看看以下问题,并决定你自己的答案。
亲自试试
传统的无监督学习数据预处理技术如 主成分分析(PCA)利用数据的共现关系来构建可以更好地表示重要模式的新特征。你是否认为 PCA 是自监督学习的一个类别,其下属于监督学习的大伞下,还是属于无监督学习的大伞下?
作者的 答案:无监督!
在 transformers 中使用的无监督学习技术包括遮蔽语言建模和下一句预测。这些任务帮助模型学习单词和句子之间的关系,使其能够更好地理解语言数据的含义和上下文。通过在遮蔽语言建模和下一句预测任务上训练的模型可以利用其对语言的理解在各种 NLP 下游任务上表现更好。这些技术在当今领先的 transformers(如在 第六章 中介绍的 DeBERTa)上已被证明具有 SoTA 的预测性能。
让我们简要介绍其他无监督预训练的例子:
-
对视觉表征进行对比学习的简单框架(SimCLR):SimCLR 利用一种称为 对比学习 的方法对卷积神经网络进行预训练。对比学习是无监督深度学习中的一种关键技术,帮助神经网络通过优化相关特征之间的距离来学习数据的表示。对比学习的核心思想是将相似数据点的特征拉近,将不相似数据点的特征推远,在特征空间中使用对比损失函数进行。尽管今天有各种形式的对比损失函数,但其一般思想是最小化相似样本之间的距离,并最大化非相似样本之间的距离。这种距离可以通过不同方式衡量,如欧氏距离或余弦距离。虽然此方法需要用于学习的标签,从技术上讲是一种监督学习损失,但所学习的特征以及对类别自由样本的相似和不相似样本的选择,使得此损失函数成为无监督深度学习中的关键技术。这种对比损失的最简单表现如下:
- 对于不相似样本:
loss = − distance
- 对于相似样本:
loss = distance
SimCLR 专注于图像数据,并使用精心设计的图像增强技术生成可以优化网络以生成更接近特征的图像对。随机裁剪和随机颜色扭曲是最通用的增强技术,可用于大多数图像数据集的 SimCLR 无监督预训练设置。
-
在同一图像的多个视角之间交换分配(SwaV):SwaV 采用类似 SimCLR 的概念,通过卷积神经网络利用图像增强技术。它还使用聚类和嵌入的概念来优化模型,确保两张图像被映射到相同的特征空间。
学习技术的执行步骤如下:
-
预设一个聚类数目,称为K。
-
使用相同的卷积神经网络对两张图像进行特征提取。
-
然后,使用一种外部技术,称为最优传输求解器(Optimal Transport Solver),通过一个嵌入层将两个特征分别分配到具体的聚类中,该嵌入层表示K聚类的代表性特征。两个特征将始终被分配到不同的聚类中。
-
计算卷积特征与所有聚类嵌入之间的点积,并应用 softmax 操作。
-
然后,将两张图像特征分配的聚类进行交换,通过交换后的聚类分配与 softmax 操作结果之间的交叉熵,来优化 CNN 和嵌入层的权重。
这个方法的核心思想是共同学习嵌入权重和卷积网络权重,确保将同一图像的多个增强版本一致地分类在一起。这项技术可以描述为对比聚类。SwaV 和 SimCLR 在多个下游任务的表现上非常接近。
-
-
SEER:SEER 是 SwaV 的结合体,使用了极大量的无标签图像数据,规模达到十亿级别,而非更常见的百万级别,并且利用高容量模型进行预训练,使用随机、未经筛选的无标签图像。这使得 SEER 在下游的有监督任务中实现了最先进的性能,并超越了单独使用 SimCLR 和 SwaV 的结果。
-
UP-DETR,由 SCTU 和腾讯微信 AI 的研究人员提出:该方法预训练了一个具有编码器-解码器架构的变换器,使用 CNN 特征进行图像目标检测任务的无监督训练。UP-DETR 成功地提升了变换器在下游有监督图像目标检测数据集上的表现。值得记住的是,它将网络结构设计为允许将随机图像块单独输入到解码器中,以预测这些图像块在原始图像上的边界框。原始图像则输入到变换器的编码器部分,并与随机图像块在解码器部分进行结合。
-
Wav2vec 2.0:Wav2vec 2.0 展示了通过利用自监督预训练作为前置任务,如何在有限的标注数据下训练出可靠的语音识别模型。它还使用了对比损失,简单来说就是样本之间的余弦相似度。该方法使用 CNN 作为音频特征提取器,并将表示量化为一个离散值数组,可以在传递到变换器之前进行训练。这里应用了遮蔽语音建模和对比损失的无监督任务。让我们看看这两种方法是如何结合在一起的:
-
随机遮蔽量化潜在语音表示的某个位置。
-
变换器在被遮蔽的量化潜在语音表示的相同位置输出,将被用作对缺失的遮蔽量化潜在语音表示的预测。
-
同一样本的非遮蔽量化潜在表示的部分将用于计算对比损失,并与预测的缺失遮蔽量化潜在语音表示进行对比,这有效地强制同一音频样本的部分内容在类似的潜在领域中。
该过程在图 9.1中进行了演示:
![]()
-
对于 PD:此图像已发送重新绘制。
图 9.1 – Wav2vec 2.0 模型结构
我们在这里介绍的方法是针对特定模态设计的。然而,经过一些努力,这些方法可以适配到其他模态以复制性能。例如,图像处理方法使用了数据增强和对比学习。要将该方法适配到基于文本的模态,可以使用保持语义的文本增强技术,例如词汇替换或回译,应该能够取得不错的效果。这里介绍的无监督方法所涉及的模态包括图像、文本数据和音频数据。这些模态因其在下游任务中的普适性而被选择。其他形式的模态,如图形数据或数值数据,可以根据具体使用案例进行高度定制,前提是没有任何信息可以转移到下游任务。在尝试运行无监督深度学习方法来创建预训练权重之前,考虑列出可以转移的信息,并定性评估是否值得继续。
那么,如果预训练网络权重已经能够在不同领域间生成非常通用的特征呢?就像在 ImageNet 数据集上以监督方式训练的 CNN 可以作为特征提取器一样,无法限制通过无监督方法(如 SwaV 或 Wav2vec 2.0)训练的网络作为特征提取器的即时使用。可以自己尝试一下!不过,一些无监督学习技术使用的神经网络是为了利用其生成的特征,而不是直接使用它们的权重。在下一节中,我们将探讨这一点。
通过无监督深度学习创建通用表示
通过无监督深度学习学到的表示可以直接用于下游监督任务,由预测监督模型使用,或者直接被最终用户使用。有一些常见的无监督方法利用神经网络,主要作为特征提取器使用。让我们来看看几个无监督特征提取器:
-
无监督预训练词标记化器:这些方法被变换器架构的多个变种广泛使用,并在第八章中引入,监督深度学习探索部分的为监督深度学习表示文本数据章节中有介绍。
-
无监督预训练词嵌入:这些方法利用无监督学习并尝试进行语言建模,类似于变换器中的掩码语言建模。然而,基于词嵌入的方法在度量性能上已被基于变换器的预训练方法和子词级文本标记化所超越。尽管如此,词嵌入方法今天仍然具有相关性,因为它在运行时效率上优于基于变换器的方法。请注意,并不是每个项目都有足够的 GPU 来在合理的运行时间内运行大型变换器。一些项目只能使用 CPU 处理,而词嵌入提供了一个完美的推理运行时与度量性能之间的权衡。此外,词嵌入是一些需要以单词为结果的用例的自然解决方案,比如从一种语言到另一种语言的词对词翻译,甚至是寻找同义词或反义词。生成预训练词嵌入的方法有fastText和word2vec。图 9.2展示了词嵌入方法的架构:

图 9.2 – 带有双层 MLP 和可训练嵌入的词嵌入架构
任务要么是根据周围单词的嵌入总和预测中间单词,要么是根据当前单词的嵌入预测周围单词。图 9.2展示了前一种情况。在使用包含 N 个单词的字典训练词嵌入时,MLP 完成训练后,MLP 会被丢弃,词嵌入将作为字典保存,方便在推理时进行简单查找。FastText 与 word2vec 的不同之处在于,它不是使用单词,而是使用子词来生成嵌入,因此能更好地处理缺失单词。FastText 的词嵌入是通过将构成完整单词的子词的嵌入相加来生成的。访问github.com/facebookresearch/fastText,了解如何使用已预训练的 157 种语言的词嵌入,或者如何在自定义数据集上预训练 FastText 嵌入!
-
自编码器:自编码器是编码器-解码器架构,可以训练用于去噪数据并减少数据的维度,同时优化数据以使其能够被重建。它们通常通过限制在架构的瓶颈部分(即编码器之后和解码器之前)的特征数量来训练,以提取有用且核心的信息。返回到第五章,理解自编码器,了解更多内容!
-
对比语言-图像预训练 (CLIP):CLIP 是一种通过对比学习训练文本语言变换器编码器和 CNN 图像编码器的方法。它提供了一个由大约 4 亿对图像文本组成的数据集,这些图像文本是通过将多个公开数据集结合而成的。该方法生成了一个强大的图像和文本特征编码器,可以独立使用。通过在训练过程中优化生成具有与 CLIP 编码图像嵌入相似的 CLIP 编码文本嵌入的图像,这种方法成为了当前文本到图像方法的主要技术之一。图 9.3展示了 CLIP 架构:

图 9.3 – CLIP 架构
这里的一个小实现细节是,图像编码器和文本编码器的输出分别送入一个线性层,以便使特征的数量匹配,从而计算对比学习损失。具体来说,对于 CLIP,交叉熵损失是以以下方式应用的:
cosine_similarity = np.dot(Image_embeddings, Text_embeddings.T) * np.exp(learned_variable)
labels = np.arange(number_of_samples)
loss_image = cross_entropy_loss(cosine_similarity, labels, axis=0)
loss_text = cross_entropy_loss(cosine_similarity, labels, axis=1)
loss = (loss_image + loss_text)/2
注意到在应用图像和文本嵌入之间的成对余弦相似度之后,仍然使用交叉熵,这展示了对比损失的多种变体,其中核心工作仍然是使用距离度量。由于 CLIP 利用了共现数据,它不属于自监督学习子类别。
-
生成模型:让我们看一些这些模型的例子:
-
变换器模型。例如,GPT-3 和 ChatGPT。两者都是经过掩蔽语言建模和下一句预测任务训练的变换器模型。与 ChatGPT 的区别在于,它是通过人类反馈和训练使用强化学习进行微调的。两者都通过自回归方式预测生成新的文本数据。
-
文本到图像生成器。例如,DALL.E 2 和 Stable Diffusion。两种方法都利用扩散模型。从高层次来看,Stable Diffusion 方法从充满噪声的基础图像开始,逐渐生成一个极高质量的图像。
-
生成对抗网络(GANs):GANs 在训练过程中使用两个神经网络组件,分别是判别器和生成器。判别器是一个分类器,用于区分真假图像。生成器和判别器会进行反复训练,直到判别器无法分辨生成器生成的图像是真是假。GANs 能够生成高质量的图像,但通常被基于扩散的模型所超越,因为后者能够生成更高质量的图像。
-
需要注意的是,使用无监督学习来学习和创建特征表示的方法并不限于基于神经网络的方法。PCA 和 TF-IDF,这些被认为是数据预处理技术,也属于这一类。然而,它们与基于深度学习的方法的主要区别在于,后者需要更多的训练时间,但提供了更好的泛化能力。
无监督表示学习的关键是利用共同出现数据之间的关系。上一节介绍的技术有公开的代码库,可以立即使用。对于大多数方法,已经有预训练的权重,可以直接利用这些权重,进一步在下游监督任务中进行微调,或作为简单的特征提取器使用。在下一节中,我们将探讨一种 CLIP 的特殊应用方式,称为零-shot 学习。
探索零-shot 学习
零-shot 学习是一种范式,涉及利用已经训练好的机器学习模型来处理新任务,而无需在新任务上进行训练或学习。该方法的核心实现了迁移学习,但与需要在下游任务中进行额外学习不同,零-shot 学习不进行任何学习。我们将在这里使用 CLIP 来实现零-shot 学习,因此它是无监督学习方法的一种扩展。
CLIP 可以用于在各种下游任务上执行零-shot 学习。回顾一下,CLIP 是通过图像-文本检索任务进行预训练的。只要 CLIP 在下游任务中应用时不进行额外的学习过程,就可以被视为零-shot 学习。经过测试的用例包括对象特征识别、视频中的动作识别、基于图像的地理定位,以及多种精细化的图像对象分类任务。此外,人们还通过一些基本方法测试并展示了零-shot 学习在物体检测中的应用。
在本章中,我们将实现一个未记录的 CLIP 零-shot 应用,即图像对象计数。计数意味着模型将执行回归任务。让我们开始实现:
-
首先,让我们导入所有必要的库。我们将使用来自
github.com/openai/CLIP的开源clip库,以利用基于视觉变换器模型的预训练 CLIP 版本:import os from tqdm import tqdm import numpy as np import clip import torch from PIL import Image import pandas as pd -
接下来,如果安装了 CUDA 工具包,我们将以 CPU 模式或 GPU 模式加载预训练的 CLIP 模型:
device = "cuda" if torch.cuda.is_available() else "cpu" model, preprocess = clip.load('ViT-B/32', device)我们在这里加载的模型变量是一个类,它包含了使用加载了预训练权重的编码器模型对图像和文本进行编码的单独方法。此外,preprocess 变量是一个在将输入传递给图像编码器之前需要执行的方法。它对训练时使用的数据进行归一化处理。
-
为了更方便地使用此模型进行预测,我们将创建一个具有以下结构的辅助方法。
def predict_with_pil_image(image_input, clip_labels, top_k=5): return similarity_score_probability, indices请记住,CLIP 被训练成以一种方式生成图像编码器和文本编码器的输出,使它们映射到相同的特征空间。将文本描述与图像匹配时,会生成文本和图像特征,它们的距离较近。由于我们使用的度量标准是余弦相似度,因此相似度值越高,距离越小。实现 CLIP 零-shot 学习的主要技巧是考虑多个描述来代表某个标签,并选择与图像的相似度得分最高的标签。然后,将该相似度得分与所有其他标签进行归一化,以获得概率得分。此外,
top_k用于控制返回多少个最高相似度得分的文本描述索引和得分。我们稍后会回到如何为零-shot 学习客观设计描述的方法,一旦我们定义了属于我们先前定义的预测方法中的代码。 -
该方法的第一部分是预处理提供的单张图像输入和多个文本描述,这些文本描述称为
clip_labels。图像将根据提供的预处理器进行预处理,而多个文本描述将根据clip库提供的文本变换器中使用的子词分词器进行分词:image_input = preprocess(cars_image).unsqueeze(0).to( device) text_inputs = torch.cat([clip.tokenize(cl) for cl in clip_labels]).to(device) -
接下来,我们将使用图像编码器和文本编码器分别对预处理后的图像和文本输入进行编码:
with torch.no_grad(): image_features = model.encode_image(image_input) text_features = model.encode_text(text_inputs)在 PyTorch 中,请记住,我们需要确保在推理模式下不计算梯度,因为我们并不是在训练模型,并且不希望浪费额外的计算或内存。
-
现在,图像和文本特征已经提取完毕,并且它们具有相同的列维度,我们将计算提供的图像输入特征与所有文本描述标签特征之间的余弦相似度得分:
image_features /= image_features.norm(dim=-1, keepdim=True) text_features /= text_features.norm(dim=-1, keepdim=True) similarity_score_probability = (100.0 * image_features @ text_features.T).softmax(dim=-1)除了相似度分数外,所有文本描述之间的相似度还通过 Softmax 进行归一化处理,以确保所有分数加起来为 1。将其视为一种方法,用于将相似度分数与其他样本进行比较。这实际上会将距离分数转化为多类预测设置,其中提供的文本描述作为标签,代表所有可能的类别。
-
接下来,我们将提取
top_k最高的相似度分数,并返回它们的相似度概率和索引,以指示该分数属于哪个标签:percentages, indices = similarity[0].topk(5) similarity_score_probability = percentages.numpy() * 100.0 indices = indices.numpy() -
现在方法已经完成,我们可以使用 Pillow 加载一张图片,创建一些描述,并将其输入到该方法中进行零-shot 学习!在本教程中,我们将进行物体计数。这可以从计数有多少辆车以正确统计停车位的可用性,到计数有多少人以统计需要为人群提供服务的人员。在本教程中,我们将把纸夹的数量作为一个小项目进行计数。请注意,这可以很容易地扩展到其他计数数据集和项目。我们将使用
www.kaggle.com/datasets/jeffheaton/count-the-paperclips?resource=download的数据集来实现这一目标。确保将数据集下载到与代码所在相同的文件夹中。让我们加载一个简单的版本,其中的纸夹少于 2 个:image = Image.open('clips-data-2020/clips/clips-25001.png')我们将得到以下输出:

图 9.4 – 纸夹计数示例:从容易(A)、中等(B)到困难(C)
图 9.4 B和图 9.4 C是更难的例子,我们将在处理完图 9.4 A的预测后再探索它们。
-
现在,我们需要多组文本描述来涵盖所有可能的纸夹计数。仅仅使用数字作为文本的简单描述不足以接近一个良好的相似度分数。相反,让我们在数字旁边添加一些额外的文本,如下所示的代码:
raw_labels = list(range(100)) clip_labels = ['{} number of paper clips where some paper clips are partially occluded'.format(label) for label in raw_labels]在这段代码中,我们使用了从 0 到 99 的所有数字,配合相同的周围文本。可以投入更多精力设计更多变化的前置文本。也可以使用多个前置文本与相同的原始标签。文本对图像的描述越详细,就越有可能找到一个描述,能产生与图像最接近的特征。
-
让我们在这个例子上运行预测,看看它的表现如何:
percentages, indices = predict_with_pil_image(image, clip_labels) print("\nTop 5 predictions:\n") for percent, index in zip(percentages, indices): print(f"{raw_labels[index]}: {percent:.2f}%") Top 5 predictions: 2: 4.64%, 4: 4.13% ,3: 4.03%, 0: 3.83%, 1: 3.67%它准确地预测了两个纸夹!
-
现在,让我们看看两个更难的例子,来自图 9.4 B和图 9.4 C:
medium_image = Image.open('clips-data-2020/clips/clips-25086.png')) hard_image = Image.open('clips-data-2020/clips/clips-25485.png') Top 5 predictions: 4: 3.98%, 3: 3.78%, 2: 3.71%, 6: 3.23%, 5: 3.16%这产生了 1 的误差,因为图像包含了三只纸夹。对图 9.4 C的预测结果如下:
Top 5 predictions: 18: 1.32%, 9: 1.29%, 0: 1.26%, 19: 1.22% ,16: 1.20%这产生了 2 的误差,因为图像包含了 16 只纸夹。还不错!
-
现在,让我们在一个划分好的验证数据集(包含 1,000 个样本)上评估平均误差:
testing_data = pd.read_csv('train.csv') errors = [] for idx, row in tqdm(testing_data.iterrows(), total=1000): image = Image.open( 'clips-data-2020/clips/clips- {}.png'.format(row['id']) ) percentages, indices = predict_with_pil_image(image, clip_labels, 1) errors.append(abs(row['clip_count'] - raw_labels[indices[0]])) if idx == 1000: break print('{} average count error'.format(np.mean(errors)))
这导致了23.8122的平均计数误差。突然之间,这看起来并不那么实用。在一些示例中,即使描述中指出会有部分遮挡,模型仍未能正确计数部分遮挡的片段。也许可以考虑添加描述,指出这些片段的大小不同,或者甚至是在一张有线纸上。试试看并做些实验吧!
在这个数据集上,使用预训练的 SqueezeNet 特征提取器单独训练一个岭回归模型,并在步骤 12中的相同验证分区上验证,结果得到了1.9102的均方根误差。这表明,仅凭零-shot 学习在此用例中并不像监督模型那样可靠,但它可能能在较简单的图像上识别并做出不错的预测。在论文中,作者强调,基于 CLIP 的零-shot 学习在一些特定的用例和数据集上可以表现良好,并达到接近监督学习技术的性能。然而,在大多数用例和数据集上,基于 CLIP 的零-shot 学习仍然远远落后于适当的监督学习方法。本教程展示了它可以工作的一个例子,但它并不那么可靠,无法在现实世界中得到应用。然而,它确实展示了 CLIP 在基础无监督方法下的潜力。再过几年,随着更多的研究,我们肯定会看到更好的性能,可能会普遍达到甚至超越监督学习的水平!
接下来,让我们探讨无监督学习中的降维组件。
探索无监督深度学习的降维组件
降维是一种在需要更快运行时间以训练和推理模型,或者当模型在处理大量数据时难以学习的情况下非常有用的技术。最著名的无监督深度学习降维方法是基于自编码器的,这一点我们在第五章《理解自编码器》中进行了讨论。典型的自编码器网络会作为一种无监督学习方法被训练,以重构输入数据。这是通过编码器-解码器结构实现的。在推理时,仅使用编码器就能执行降维操作,因为编码器的输出会包含最紧凑的表示,这些表示可以完全重建原始输入数据。自编码器支持不同的模态,每次只能使用一种模态,这使得它成为一种非常灵活的无监督降维方法。
其他例子包括使用浅层神经网络创建词嵌入的无监督方法,例如 FastText 或 Word2vec。语言中存在的独特词汇数量是巨大的。即使将其缩小到训练数据中的唯一词汇总数,这个数字也可以轻易膨胀到 100,000 个词。编码这些词的一种简单方法是使用 one-hot 编码或 TF-IDF。当数据集中包含 100,000 个独特词汇时,这两种方法会产生 100,000 列特征。这将轻松增加内存需求,并可能决定解决方案的可行性。然而,词嵌入可以进行调整,选择在预训练语言模型时所需的特征列数。
最后,让我们学习如何检测外部数据中的异常。
检测外部数据中的异常
异常检测通常也被认为是无监督学习的重要应用之一。异常检测可以用于需要对现有数据进行任何形式过滤的情况,称为离群值检测,并且还可以作为推理阶段的实时检测器,检测新的外部数据,这被称为新颖性检测。以下是异常检测的最终用户应用实例:
-
去除数据集中的噪声,以便将其输入到有监督的特征学习过程中,从而实现更稳定的学习。
-
在生产线上去除缺陷产品。这可以涵盖从半导体晶圆的制造生产到蛋类生产的各个领域。
-
通过检测异常交易来预防欺诈。
-
通过短信、电子邮件或直接消息平台进行诈骗检测。
异常检测是一个二分类问题。这意味着人们在处理这些列举的例子时,常常采用有监督学习方法,收集大量的负样本和正样本。当已经收集并标注了包含负/异常数据的高质量数据集时,有监督学习方法会表现得很好,因为通常情况下,已经存在相当多的正/正常数据。请回到第一章的准备数据部分,复习一下什么是机器学习中的高质量数据!然而,实际上,我们不可能捕捉到所有可能出现的负样本和类型。异常检测特定算法被设计用来更通用地处理异常或负样本,并且能够很好地推断出未见过的示例。
用于在未用于训练的外部数据上实现异常检测的较为广为人知的深度学习方法之一是自动编码器模型。该模型执行所谓的单类分类器,类似于传统的单类支持向量机。这意味着自动编码器可以训练以重构输入的原始数据,以实现四种任务中的一种或多种组合——降维、去噪、通用特征提取器和异常检测。根据手头的目标,应该以不同方式训练自动编码器。对于降维、去噪和通用特征提取器,应该像之前介绍的那样正常训练自动编码器。对于异常检测,需要执行以下步骤以确保其实现期望的行为:
-
无异常的数据将用作训练数据。如果可能存在异常,请使用简单的异常值检测算法,如局部离群因子,并移除数据中的异常值。
-
使用异常和非异常数据的组合形成验证和保留分区。如果事先不知道这种分类,可以使用步骤 1中的基于异常的异常检测算法对数据进行标记。
-
如果步骤 2不可行,请使用均方误差损失训练和过度拟合训练数据。这样可以确保模型只能重构具有与训练数据相同特征的数据。过拟合可以通过每个时期训练并确保训练重构损失降低来完成。
-
如果步骤 2可行,请使用给定的交叉验证分区策略进行训练、验证和评估,并按照前一章节中有效训练监督深度学习模型部分介绍的提示进行操作。
-
自动编码器的预测功能用于测试、验证和推断,可以根据模型通过均方误差重现输入数据的能力进行设置。需要使用阈值作为截断机制来确定哪些数据是异常的。均方误差越高,提供的数据是异常的可能性就越大。由于异常检测是二元分类问题,一旦标签可用,应使用 ROC 曲线、混淆矩阵和使用不同均方误差阈值的对数损失错误来进行性能分析。这将在下一章更全面地介绍。
在一般的异常检测中,需要注意的是,异常是一种模糊的描述,指的是什么样的数据被认为是异常。不同的算法根据数据集、特征空间和特征分布对异常有不同的定义。当一个算法在你预定义的异常情况下表现不佳时,并不意味着该算法在一般情况下就不是一个好的模型。自编码器方法并不总是能够最好地捕捉你对异常的直观理解。在无监督异常检测学习中,确保你训练多个模型,并使用适当的超参数设置,分别分析哪些模型符合你对数据异常性的直觉。
总结
深度学习对无监督学习领域作出了重大贡献,推动了几种创新方法的发展。但迄今为止,最具影响力的方法是无监督预训练,它利用当今互联网上丰富的免费数据来提升下游监督任务的模型性能,并创建具有普适性的表示。随着研究的深入和时间的推移,无监督学习将在缩小通往通用人工智能的差距方面发挥重要作用。总体而言,深度学习在无监督学习领域已成为一种宝贵的工具,帮助从业者充分利用互联网上大量的免费数据。
在下一章,我们将深入探讨本书第二部分的第一章,该章节旨在介绍提供关于已训练深度学习模型洞察的方法。
第二部分 – 多模态模型洞察
在本书的这一部分,我们深入探讨了多模态模型洞察的迷人世界,带你全面了解评估、解释和保护深度学习模型的各个方面。本部分提供了关于模型评估与提升的全面理解,同时强调了在现实应用中负责且有效地部署 AI 的重要性。在这些章节中,你将探索评估和理解模型预测的方法、解释神经网络,并解决伦理和安全问题,如偏见、公平性和对抗性表现。
到本部分结束时,你将全面理解模型评估、解释和安全性的重要性,使你能够创建健壮、可靠、公平的深度学习系统和解决方案,这些系统和解决方案不仅在性能上表现出色,还能考虑伦理问题和潜在的漏洞,同时经得起现实应用中的考验。
本部分包含以下章节:
-
第十章,探索模型评估方法
-
第十一章,解释神经网络预测
-
第十二章,解释神经网络
-
第十三章,探索偏见与公平性
-
第十四章,分析对抗性表现
第十章:探索模型评估方法
一个没有经过任何验证的训练深度学习模型不能部署到生产环境中。在机器学习软件领域,生产指的是将机器学习模型部署到真实环境中并实际使用其预测结果。更广泛地说,模型评估在任何深度学习项目中都起着至关重要的作用。通常,一个深度学习项目会构建多个模型,最终选择一个模型在生产环境中使用。任何项目的良好模型评估过程都能带来以下结果:
-
通过模型比较和评估指标得到更好的最终模型性能
-
通过了解常见的模型陷阱,减少生产预测中的错误
-
通过模型洞察使得实践者与最终模型的行为更加一致
-
通过成功指标评估提高项目成功的概率
-
一个偏差更小、更公平并且能够产生更可信预测的最终模型
通常,模型评估过程能在整个机器学习生命周期中帮助做出更明智的决策。在本书第二部分的第一章中,我们将讨论所有有助于实现这些收益的模型评估类别。此外,在接下来的四章中,我们将深入探讨这些类别,这些章节都属于本书的第二部分。具体来说,我们将在本章讨论以下主题:
-
探索不同的模型评估方法
-
构建基础模型评估指标
-
探索自定义指标及其应用
-
探索用于比较模型指标的统计测试
-
将评估指标与成功关联起来
-
直接优化指标
技术要求
本章将通过使用 Python 编程语言进行实际操作。要完成这一部分,你只需在 Python 中安装matplotlib库。
代码文件可在 GitHub 上找到,链接为github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_10。
探索不同的模型评估方法
大多数实践者都熟悉与准确性相关的指标。这是最基本的评估方法。通常,对于监督学习问题,实践者会将准确性相关的指标视为黄金标准。在模型评估的背景下,“准确性指标”这一术语通常用于统称各种性能指标,如准确率、F1 分数、召回率、精度和均方误差。结合适当的交叉验证划分策略,使用指标作为独立评估策略,在大多数项目中可以取得很好的效果。在深度学习中,准确性相关的指标通常用于监控每个周期中模型的进展。随后,监控过程可以扩展到执行早停,当模型不再改善时停止训练,并确定何时减少学习率。此外,在训练过程结束时,可以加载最佳的模型权重,这些权重是在验证数据集上取得最佳指标分数时的权重。
仅仅依靠与准确性相关的指标并不能完整地反映一个机器学习模型的能力和行为。特别是在无监督项目中,准确性指标是表面性的,只对特定分布相关。对模型进行更全面的理解,可以让你在机器的生命周期中做出更明智的决策。以下是一些获得更深入理解模型的方式:
-
模型洞察可以作为评估所用数据准确性的代理。如果数据被认为是不准确的或存在一些轻微缺陷,可以重新进入机器学习生命周期中的数据准备阶段。
-
如果检测到偏差,可能需要重新训练模型以去除偏差。
-
评估模型是否能够像领域专家一样识别模式同样很重要。如果不能,可能需要采用不同的模型或数据准备方法。
-
需要考虑模型是否能够在预测中体现常识。如果不能,可能需要应用特殊的后处理技术来强制执行常识。
-
暴露其他性能指标,如推理速度和模型大小,在选择模型时可能至关重要。你不希望选择一个过慢或过大的模型,导致无法适应目标生产环境。
除了帮助项目朝着成功方向发展外,从模型中收集洞见还可以帮助早期识别潜在问题。在第一章中介绍的机器学习生命周期,深度学习生命周期,中,重要的是要记住,项目可能在规划阶段或在提供模型洞见时失败。失败是机器学习过程中的自然组成部分,事实上,许多项目注定不会成功。这可能是由于各种因素,如数据工程不适合机器学习或任务过于复杂。然而,重要的是要尽快失败并放弃该项目,从而将资源重新分配到更有成功机会的其他用例中。在项目至关重要且无法放弃的情况下,快速识别失败的根本原因可以通过将资源引导到修复问题上,并循环地在机器学习生命周期的各个阶段之间过渡,从而提高项目的执行效率。为了让项目能够快速失败,必须有一种负责任且有信心的方式来判断模型是否无法正常工作。总之,这种快速失败的能力是非常有益的,因为它节省了本可能浪费的时间和资源。
以下列表展示了一系列足够广泛的方法,可用于评估深度学习模型,其中一些方法也是通用的,适用于非深度学习模型:
-
评估指标工程:虽然评估指标在许多项目中常常被使用,但评估指标工程的实践通常被忽视。在本章中,我们将仔细探讨指标工程,并探索选择合适评估指标的过程。我们将首先讨论适用于各种问题类型的基础基线评估指标。然后,我们将进一步探讨如何将基线评估指标升级为适合项目领域和用例的特定指标。简而言之,本章将帮助你理解指标工程的重要性,并指导你选择适合项目的评估指标。
-
学习曲线:学习曲线用于确定深度学习模型的拟合程度。
-
提升图:提升图提供了预测模型性能的可视化表示。它显示了模型在预测正向结果方面比随机机会要好多少。
-
接收者操作特征(ROC)曲线:二分类模型性能的图形表示。它们在不同的分类阈值下绘制真正例率(TPR)与假正例率(FPR)。
-
混淆矩阵:混淆矩阵是一种性能评估工具,用于测量机器学习模型的分类准确度。它比较模型预测和实际结果,并以矩阵格式呈现。
-
特征重要性:这是确定数据集中哪些特征对机器学习模型输出影响最大的过程。此外,它有助于识别给定问题中最重要的因素,并可以帮助提高模型的整体性能。
-
A/B 测试:机器学习的 A/B 测试涉及比较两种不同模型或算法在特定任务上的表现,以确定哪个模型在实践中表现更好。这可以帮助从业者更明智地决定使用哪些模型或如何改进现有模型。
-
队列分析:队列分析是一种评估模型在不同用户子群或队列上表现的技术。它可以帮助确定模型是否对不同群体表现出不同的性能,并且可以用于了解如何针对特定分段改进模型。
-
残差分析:残差分析是一种通过检查观测值与预测值(残差)之间的差异来检查回归模型拟合优度的技术。它有助于识别残差中可能指示模型改进方向的模式或异常值。
-
置信区间:置信区间是估计中不确定性的一种度量。它可以用于确定在一定置信水平下,模型的真实性能可能落在的值范围内。置信区间对于比较不同模型的性能或确定模型性能是否具有统计显著性很有用。
-
从预测中获取见解:这将在第十一章,解释神经网络预测中进行讨论。
-
解释神经网络:这将在第十二章,解释神经网络中进行讨论。
-
偏见和公平性分析:这将在第十三章,探索偏见和公平性中进行讨论。
-
对抗分析:这将在第十四章,分析对抗性能中进行讨论。
在本书中,我们只会涵盖某种方式与神经网络相关的方法。在下一节中,我们将从工程基线评估方法开始,这是模型评估的度量标准。
工程基础模型评估指标
为你的使用场景设计一个度量标准是一项常常被忽视的技能。这很可能是因为大多数项目使用的是公开可用的数据集,这些数据集几乎总是已经有了一个度量标准。包括 Kaggle 上的项目和许多人用来进行基准测试的公共数据集。然而,现实生活中并不是这样,度量标准并不会直接提供给你。让我们在这里进一步探讨这个话题,掌握这一技能。
模型评估指标是监督学习项目中至关重要的首要评估方法,排除无监督学习项目。根据问题和目标类型,有一些基准指标被广泛使用,成为事实标准。此外,也有一些定制化版本的基准指标,专门针对特殊目标。例如,生成式任务可以通过一种名为平均意见分数(mean opinion score)的特殊人工评分来进行评估。这里推荐的策略是:总是从基准指标开始,然后逐步调整到能够反映错误在不同条件下如何合理分布的指标,这类似于推荐如何构建模型。
以下是不同条件下的基准指标:
-
二分类问题:
-
准确率:这是所有样本中被正确分类的示例(真阳性和真阴性)的百分比。它是各个领域中最广泛知晓的评估指标。然而,实际上,这是一个偏颇的指标,可能会因为大多数数据集中自然存在的负样本过多,掩盖模型实际的正向预测表现。如果数据集中有 99 个负样本和 1 个正样本,仅仅通过始终预测为负样本,就能获得 99% 的准确率!准确率仍然是模型评估的主要方法,但它并不总是能实际使用。当有人说模型是准确的,他们可能并不是在使用准确率这一指标。
-
精确率:这是所有预测为正样本的示例中,真阳性占的比例。它是一个健壮的替代指标,重点关注假阳性。这里对于二分类项目需要设置一个预测阈值。
-
召回率:这是所有实际为正样本的示例中,真阳性占的比例。它是一个健壮的替代指标,重点关注假阴性。这里对于二分类项目需要设置一个预测阈值。
-
F1 分数:F1 分数是精确率和召回率的调和均值。它提供了一个平衡的模型性能衡量标准。调和均值的公式是
-
n _ 1 _ x1+ 1 _ x2… 1 _ xn
其中,表示样本总数,表示单个样本的值。还有 F2 分数,它对召回率的权重高于精度。如果你对假阳性和假阴性同样重视,可以使用 F1 分数;如果更关注假阴性,可以使用 F2 分数。例如,在入侵检测的应用场景中,如果你想捕捉到所有的入侵者,不能容忍假阴性,但假阳性是可以接受的,因此使用 F2 分数会更合适。注意,使用的是调和平均而非算术平均,以确保对极端值进行惩罚。例如,召回率为 1.0 而精度为 0.01 时,结果将是 0.012,而不是接近 0.5。这里需要一个预测阈值。
-
接收者操作特征曲线下面积(AUC ROC):这是 ROC 曲线下的面积,提供了衡量模型区分正例与负例能力的标准。这里不需要阈值。推荐在正负类别平衡时使用,因为不需要像 F1 和 F2 那样调整预测阈值。
-
平均精度(mAP):这是精度度量的扩展,在这里,除了使用单一阈值,还使用多个阈值来计算精度,并对结果取平均,以获得在不同阈值下更稳健的精度值。对于多类别问题,平均精度会独立计算每个类别,并取平均得到 mAP。
-
多分类问题:
-
宏平均:分别计算每个类别的二分类指标,然后对它们取平均。
-
微平均:通过考虑预测类别中最高的类别,确定整体的真阳性率和假阳性率,然后使用这些比率来计算整体的精度。
-
-
回归问题:
-
均方误差(MSE):这是预测值与实际值之间的平方差的平均值。
-
均方根误差(RMSE):这是均方误差(MSE)的平方根。它提供与目标数据相同尺度的值,通常推荐使用 RMSE 而非 MSE。
-
平均绝对误差(MAE):这是预测值与实际值之间绝对差的平均值。当你关心预测值与实际标签之间的差异,而不关心差异的正负时,使用 MAE 优于 RMSE。
-
R 平方:衡量模型拟合数据的程度,其值范围从 0(拟合差)到 1(拟合完美)。
-
-
多标签问题:
- 标签排序平均精度 (LRAP):这是对每个真实标签分配给特定样本的平均精度。它考虑了预测标签与真实标签的排名,并根据真实标签在排名中的远近适当地分配分数。LRAP 适用于电影推荐系统等用例,在这些用例中,预测多个标签及其排名非常重要。它评估模型预测正确标签及其相关性顺序的能力,是需要精确且有意义排名的任务(如类别推荐)的理想度量。
-
图像和视频标签:当直接将原始图像和视频帧用作标签时,需使用一套自定义度量标准,能够提供比标准回归度量更有意义的评估。这些标准如下:
-
峰值信噪比 (PSNR):这是衡量重建图像或视频质量的度量标准,基于原始图像与重建图像之间的差异。
-
结构相似性指数 (SSIM):这是衡量两张图像或视频帧之间结构相似性的度量标准。
-
-
文本标签:尽管标准的分类损失和度量可用于文本预测任务,但一些度量可以衡量更高级的概念,这些概念更符合人类直觉。具体包括:
-
蓝色分数:这是衡量机器生成文本和人类生成文本之间相似性的度量,基于 n-gram 重叠。
-
词错误率 (WER):这是自动语音识别系统中的错误率衡量标准,基于词序列中的错误数量。
-
-
基于人类质量的度量:这些是无法通过编程计算的度量,必须由人工手动评估:
-
平均意见分数 (MOS):这是由人类观察者给予的主观质量评分,可以用于验证和校准客观质量度量。
-
用户参与度:可以使用网站或应用上的停留时间、点击率或跳出率等度量来评估用户参与度和满意度。
-
任务完成率:这是成功完成特定任务或目标的用户比例,可用于评估产品或服务的可用性和有效性。
-
基准度量是根据你的问题类型和条件,应该首先选择的一组常见度量。话虽如此,选择使用哪个基准度量仍然取决于具体问题,以及模型性能不同方面之间的重要权衡。以下是逐步建议,帮助你如何选择和使用合适的模型评估度量:
-
理解问题。考虑问题的性质、数据和期望的结果。这是一个关键步骤,它将帮助你识别出对任务最重要的关键标准。
-
留意数据的质量。在第一章中介绍的数据质量支柱(代表性、一致性、全面性、唯一性、公平性和有效性)会影响度量标准所代表的实际意义。如果你在一个不良数据集上评估模型的性能,那么所选择的度量标准可能无法反映模型在真实世界数据上的真实表现。
-
考虑与模型或模型输出进行交互的用户的视角。他们的期望和需求是什么?需要考虑哪些相关的质量因素?
-
明确定义预测需要实现的目标以及它们需要避免的目标。
-
-
选择与定义目标一致的度量标准。你选择的度量标准应该与你的整体目标一致。例如,在二分类医学用例中用于检测癌症,错误地做出癌症假阳性诊断可能会毁掉患者多年的生活,因此应选择诸如精准度等度量标准,以量化减少假阳性。
-
考虑在类似问题中常用的基础度量标准,以及它们如何需要适应或修改以适应当前的问题。这个问题是否有任何独特的方面需要不同类型的度量标准或对现有度量标准的修改?
-
单一度量标准可能无法全面捕捉模型的表现。考虑使用多个度量标准来评估模型表现的不同方面。一个明显的例子是创建一个将精准度和召回率两个度量标准结合的 F1 分数。
-
-
考虑度量标准之间的权衡。例如,在二分类项目中,通过修改模型的预测阈值提高召回率的表现可能会不利于精准度。评估度量标准之间的权衡(如果有的话),并选择最能与目标对齐的度量标准。
-
交叉验证度量标准。确保度量标准是在验证集和持出集上计算的,而不仅仅是在训练集上,这对于估计模型在真实世界中的表现至关重要。在每个 epoch 中计算训练数据和验证数据上的度量标准,也能让你通过所选度量标准直接可视化学习曲线,而不是使用已采用的损失函数。
-
考虑直接针对度量优化你的模型。有些度量可以在深度学习库中直接近似重现,并作为损失函数使用。直接优化有时可以帮助你获得一个更适合你选择的度量的模型。然而,也可能会出现一些陷阱,我们将在后面的直接优化度量部分详细讨论这一点。
-
构建、评估并比较许多模型,针对评估度量通过迭代改进或使用多种技术进行调整。构建机器学习模型的过程可能只是整个 ML 生命周期中最短的过程。但请记住,你在构建和实验过程中投入的努力可以决定项目的成败。构建多种模型并迭代改进可能是一个艰难且耗时的任务。拥有可以适应大多数应用场景的模板代码,可以让这个过程变得更加高效和无缝,这样你就能将精力集中在项目中更为紧迫的问题上。或者,你也可以考虑使用 AutoML 工具来持续为每个应用场景训练不同的模型。期待本书最后一章,我们将感受 AutoML 工具如何简化模型构建过程!
-
确保你定义与所选度量相关的成功标准。将所选评估度量转化为实际的成功度量时,重要的是要清楚地了解对于给定问题,什么才算成功。这通常涉及定义一个模型需要达到的性能阈值或目标水平,才能被认为是成功的。有时,成功标准可以根据特定类型的错误或特定数据组有更细致的定义。我们将在本章后面深入探讨这一点。
通过仔细考虑这些建议,可以选择一个独特的度量,准确衡量问题的相关方面。最终,这有助于开发更准确、更有效的机器学习解决方案,从而带来更好的性能和更成功的结果。
基础度量是一组常用的度量,许多从业人员使用它们来评估模型的性能。然而,在某些情况下,特定问题可能有额外的标准和独特的行为,需要在评估模型性能时加以考虑。在这种情况下,可能需要采用替代的或定制的度量,这些度量更适合该问题的具体需求。基础度量可以根据你希望用来评判模型的额外标准进一步调整。下一部分将探讨自定义度量及其应用,包括在何种情况下将其作为最合适的度量来用于特定的应用场景。
探索自定义度量及其应用
基本指标通常足以满足大多数使用场景的需求。然而,自定义指标是在基本指标的基础上构建的,融入了特定场景下的附加目标。可以将基本指标视为本科学位,而将自定义指标视为硕士或博士学位。如果基本指标满足你的需求,并且没有其他额外要求,那么只使用基本指标也是完全可以的。
自定义指标通常在项目初期自然出现,并且高度依赖于具体的应用场景。大多数实际应用场景不会公开其选择的指标,即使模型的预测最终是公开使用的,比如Open AI的ChatGPT。然而,在机器学习竞赛中,拥有实际应用场景和数据的公司会公开其选择的指标,以便找到可以建立的最佳模型。在这样的竞赛项目中,举办比赛的公司有动力进行高质量的指标工程工作,以反映其应用场景的理想。一个指标可以影响最终的最佳模型,如果公司没有工程出与其理想匹配的好指标,最终会花费大量资金。一些比赛提供高达 100,000 美元的奖金!
在本节中,我们将介绍一些常见且公开分享的自定义指标,以及来自机器学习竞赛的相关度量标准和使用案例,这些对于你考虑特定应用场景可能会有所帮助:
| 理想 | 使用案例 | 自定义指标 |
|---|---|---|
| 对于基于时间序列回归的点预测,目标是季节性的,并且可能会根据数据所在的季节大幅波动。我们需要一个度量标准,确保错误不会对任何一个季节的权重过重。 | M5 预测—准确性(Kaggle):这涉及到预测沃尔玛零售商品的销量。该比赛提供了沃尔玛的销售时间序列数据,数据遵循层级结构,从商品层级开始,逐步向部门、产品类别和门店层级扩展。数据慷慨地提供,覆盖了美国的三个地区:加利福尼亚、德克萨斯和威斯康星州。 | 加权均方根标准化误差(WRMSSE):这里的主要部分是 RMSSE,它是 RMSE 的一个修改版。在应用均方误差的平方根之前,RMSSE 将标准的 MSE 除以使用最近观测作为真实值的 MSE。这确保了任何季节的 RMSE 都会被缩放到相同的值域。 |
| 有些标签/类别在实际中并没有那么重要,可能是因为它们的出现频率较低,或者它们对预测后的决策影响不大。不要过度评价不重要标签的模型表现;应将更多关注放在更重要标签/类别的错误上。 | any标签用于考虑所有其他未使用特定标签表示的出血类型。any标签的数据量是其他任何单一标签的 2-3 倍。 |
any标签的权重比其他任何标签都要高,尽管它的数据量更多。这表明,标签对指标的重要性并不单纯依赖于与该标签相关的数据稀缺性;它实际上取决于特定问题的上下文。 |
| M5 预测——准确度(Kaggle):这涉及到预测沃尔玛零售商品的销售数量。竞赛提供了沃尔玛的时间序列销售数据,这些数据遵循分层结构,从商品级别开始,逐步扩展到部门、产品类别和门店级别。数据慷慨提供,涵盖了美国的三个地区:加利福尼亚、德克萨斯和威斯康星州。 | WRMSSE:竞赛的组织者更重视那些带来更多销售额的产品的单位销售预测。每个产品的权重是通过使用训练样本最后 28 个观察值中的产品销售量(销售单位的总和乘以其各自的价格)来获得的。 | |
| 沃尔玛招聘——门店销售预测(Kaggle):这涉及到沃尔玛商品的销售预测。 | 加权绝对误差(WMAE):沃尔玛将假期周的预测误差加权为非假期周的五倍,因为假期周的销售额远高于非假期周。 | |
| 我们其实并不太在意小的错误,因为它们可以被容忍,但我们关心的是大的错误,因为它们可能会导致触发不想要的操作,消耗了预测结果。 | 谷歌分析客户收入预测(Kaggle):这是一个回归问题,旨在预测在线商店中一个客户的总收入。收入值高度偏斜且包含许多零值,这使得使用传统的度量标准(如 MSE 或 MAE)来评估参与模型的表现变得具有挑战性。 | 均方根对数误差(RMSLE):RMSLE 在计算 RMSE 之前,对预测值和实际值应用对数变换,这有助于比小错误更严重地惩罚大错误。实现这一点的自然方式是简单地训练模型去预测目标的对数值,并应用 RMSE 来实现 RMSLE。 |
| 糖尿病视网膜病变检测(Kaggle):这是一个多类问题,旨在预测高分辨率视网膜图像是否存在糖尿病视网膜病变。该问题有五个类别:一个表示无病,其他四个表示不同程度的病变。 | 二次加权卡帕系数(QWK):卡帕系数是一种统计度量,用于量化多类分类任务中预测标签与实际标签之间的一致程度。二次加权机制使卡帕系数对小错误更为稳健,对大错误更为敏感。二次加权方案可以解决当高分类一致性导致卡帕系数膨胀的问题,同时仍能准确表示更难分类的情况的一致性水平。 | |
| Two Sigma Connect 租赁列表查询(Kaggle):这是一个基于房源内容(如文字描述、照片、卧室数量和价格)预测公寓租赁列表受欢迎程度的问题。 | 对数损失(Log loss):对数损失是一种评估指标,它通过惩罚那些对错误预测过于自信的模型,来强调错误预测的重要性。 | |
| 在一个多类问题中,我们对多类模型的容忍度较高,因为在我们的使用案例中,我们可以使用多个预测类别,而不是仅使用最可能的单一类别,以最大化模型的真正正例命中率表现。 | Airbnb 新用户预订(Kaggle):这是一个多类问题,旨在预测新用户将在哪个国家/地区首次进行 Airbnb 预订(包括无预订类别)。预测的类别将帮助 Airbnb 为其社区提供更个性化的内容,缩短首次预订的平均时间,并更好地预测需求。Airbnb 对模型产生的多类预测有较为宽松的要求,可以根据前五个国家而非单一国家执行个性化内容,以提高真正正例命中率。 | 标准化折扣累计增益(NDCG)前 k 类:NDCG 是衡量前 k 类排序效果的指标,通常用于推荐系统。该指标与 Airbnb 对多类预测的容忍度相匹配。它适用于任何能够容忍使用多个预测类别,而不是仅使用最预测类别的多类使用案例。 |
| 对于基于视频的多目标跟踪应用场景,我们希望使用一种衡量标准,能够惩罚不希望出现的跟踪行为,例如错误的物体识别和未能持续稳定地跟踪物体。此外,我们需要一种可以通过模型随着时间生成的多个轨迹与固定的真实轨迹集进行比较来计算的度量标准。 | Track 1,我们创建了一个大规模的合成数据集,包含动画人物。我们数据集中的所有摄像机视频源都具有高清分辨率(1080p),帧率为每秒 30 帧。 |
身份 F1 得分(IDF1):IDF1 通过评估跟踪算法的整体表现来处理此问题,具体是根据它将预测轨迹与真实轨迹匹配的效果,考虑到每个物体的身份随时间变化。该算法会惩罚假阳性、假阴性以及身份切换(即算法错误地将两个检测结果当作同一物体,或错误地将两个不同的物体身份归为同一个物体)。最重要的是,模型可以基于交并比(IOU)算法的一个版本,动态地确定匹配轨迹,该算法比较预测轨迹与真实轨迹之间的交集和并集。 |
| 我们更关心模型性能中的偏差,而非整体模型的表现。 | Jigsaw 意外偏差在毒性分类中的应用(Kaggle) www.kaggle.com/competitions/jigsaw-unintended-bias-in-toxicity-classification/overview/evaluation:这项任务涉及预测文本数据中毒性的不同强度。数据集包含竞争作者希望在偏差优化方面进行处理的身份。涉及的身份包括男性、女性、跨性别、其他性别、异性恋、同性恋(男同性恋或女同性恋)、双性恋、其他性取向、基督教徒、犹太教徒、穆斯林、印度教徒、佛教徒、无神论者、其他宗教、黑人、白人、亚洲人、拉丁裔、其他种族或族裔、身体残疾、智力或学习障碍、精神或心理疾病以及其他残疾。 |
偏差焦点 AUC:该竞赛关注多个 AUC 度量的加权组合,这些 AUC 度量是基于数据集中不同身份的特定子集计算的,这些身份可以在文本数据行中提到。总体而言,AUC 与接下来的三个偏差焦点度量等权结合。基于身份的子群 AUC:这涉及分析仅包含提到特定身份子群的评论的数据集。在该度量中得分较低表示模型难以区分与该身份相关的有毒评论和无毒评论。 |
| 背景正例,子群负例(BPSN)AUC:这涉及评估模型在提到身份的非毒性示例和不提到身份的毒性示例上的表现。此指标得分较低表示模型可能错误地将与身份相关的非毒性示例标记为毒性示例。 | ||
| 背景负例,子群正例(BNSP)AUC:这涉及评估模型在提到身份的毒性示例和不提到身份的非毒性示例上的表现。此指标得分较低表示模型可能错误地将与身份相关的毒性示例标记为非毒性示例。 |
表 10.1 – 自定义理想的表格,包含示例用例和使用的指标
在第二个理想中,通用的指标思想是根据您认为更重要的内容对任何选择的指标应用权重。此外,权重还可以更加灵活地应用于任何不作为模型输入或目标的数据。最后,关于最后一个理想,期待第十三章,探索偏见和公平性,以发现优化偏见的方法。
尽管我们讨论的自定义理想和指标示例是有用的指南,但重要的是要记住,可能有许多不同的指标和理想与您的特定用例相关。不要害怕深入挖掘您的问题领域,找出对您的特定情况独特的指标。
我们提供的示例可以作为开发量身定制的指标的有用备忘单。通过理解在特定领域使用这些特殊指标背后的原因,您可以深入了解在评估模型性能时应考虑的因素。最终,关键是选择与您的目标一致、并能够捕捉到问题领域中最重要方面的指标。
接下来,我们将讨论一种强健的策略,用于比较不同模型在多个指标值下的性能,这些指标值是从不同的交叉验证折叠或数据集划分中计算得出的。
探索用于比较模型指标的统计检验
在机器学习中,基于指标的模型评估通常涉及使用来自不同折叠或划分(如保留集和验证集)的聚合指标的平均值,以比较各种模型的性能。然而,仅依赖这些平均性能指标可能无法提供对模型性能和泛化能力的全面评估。一个更强健的模型评估方法是引入统计假设检验,评估观察到的性能差异是否具有统计显著性,或是否只是由于随机机会造成的。
统计假设检验是用来确定观察到的数据是否提供了足够的证据以拒绝零假设,支持替代假设的程序,它帮助量化观察到的差异是否由随机机会或真实效应造成。在统计检验中,零假设(H0)是默认假设,表示变量之间没有效应或关系,作为与替代假设进行比较的基础,目的是确定观察到的数据是否提供了足够的证据以拒绝这一默认假设。在比较多个分区和数据集中的模型指标性能时,零假设通常是模型的性能之间没有差异,而替代假设则是存在差异。
总体而言,统计检验提供了一个正式的框架,以客观地判断性能差异是否显著或仅为偶然。此外,统计检验通过考虑指标中的变异性和不确定性,提供了对模型性能的全面理解。下表展示了常见的统计检验方法,包括执行所需的 Python 代码、如何解释结果以及何时使用它们:
| 统计检验 | Python 代码 | 结果解释 | 推荐使用 |
|---|---|---|---|
| 配对 t 检验 | from scipy.stats import ttest_rel``t_stat, p_value = ttest_rel(model1_scores, model2_scores) |
如果 p_value < 0.05,模型之间存在显著差异。 |
用于比较两个依赖样本,且差异呈正态分布时。 |
| Mann-Whitney U 检验 | from scipy.stats import mannwhitneyu``u_stat, p_value = mannwhitneyu(model1_scores, model2_scores) |
如果 p_value < 0.05,模型之间存在显著差异。 |
用于比较两个独立样本,且数据为非正态分布或序数数据时。 |
| 方差分析 (ANOVA) | from scipy.stats import f_oneway f_stat, p_value = f_oneway(model1_scores, model2_scores, model3_scores) |
如果 p_value < 0.05,模型之间存在显著差异。 |
用于比较三个或更多独立样本,且数据呈正态分布且方差相等时。 |
| Kruskal-Wallis H 检验 | from scipy.stats import kruskal``h_stat, p_value = kruskal(model1_scores, model2_scores, model3_scores) |
如果 p_value < 0.05,模型之间存在显著差异。 |
用于比较三个或更多独立样本,且数据为非正态分布或序数数据时。 |
表 10.2 – 常见统计检验方法及其 Python 实现、结果解释和推荐使用场景
这些建议可以帮助你根据数据的条件和假设选择适当的统计检验方法,例如样本数量、数据类型(依赖性或独立性)以及数据的分布(正态分布或非正态分布)。
接下来,我们将讨论如何将度量工程的结果转化为成功标准。
将评估指标与成功联系起来
在机器学习项目中,定义成功是至关重要的,应当在项目初期就进行定义,正如在第一章的定义成功部分中介绍的那样,深度学习生命周期。成功可以定义为实现更高层次的目标,例如提高流程的效率或相比人工劳动提高流程的准确性。在一些罕见的情况下,机器学习可以使得以前由于人类限制无法实现的流程成为可能。实现这些目标的最终成功是为组织节省成本或创造更多的收入。
一个模型的度量性能得分为 0.80 的 F1 分数或 0.00123 的 RMSE,实际上并没有直接意义,需要在使用案例中转化为可衡量的内容。例如,应该回答诸如什么样的估计模型得分可以使项目实现预定的成本节约或收入增长的问题。从模型性能中量化可以获得的成功是至关重要的,特别是在机器学习项目可能很昂贵的情况下。如果模型无法达到一定的性能水平,投资回报率可能会很低。选择评估指标后,重要的是要为成功设定一个现实、可达的指标阈值,并且该阈值应当基于业务目标。
作为本主题的总结,我们通过一个示例工作流来将评估指标与基于假设使用案例的成功联系起来。
假设我们要考虑一个使用图像数据来识别制造过程中的缺陷产品的使用案例。假设生产一个产品的成本为 50 美元,而其零售价格为 200 美元。如果该产品是有缺陷的并且被送到客户手中,那么将导致 1000 美元的额外退款费用,并且需要退还客户支付的 200 美元,以补偿缺陷产品可能造成的损害。另一方面,如果一个合格的产品被误判为缺陷并被报废,那么就会产生 250 美元的成本;其中 50 美元用于生产该产品,200 美元则是作为报废损失的机会成本。
假设我们有一个包含 10,000 张产品图像的数据集,这是每月生产的产品数量。如果我们不使用模型,生产并运输所有 10,000 个产品,我们将有 95 个有缺陷的产品,这将导致 $500,000 的生产成本(10,000 x $50)和 $95,000 的退货费用,总成本为 $595,000。销售所有非缺陷产品后,公司将获得 $1,981,000 的销售收入(9,905 x $200)。总收入将是 $1,386,000。
假设我们使用一个训练好的深度学习模型来将这些图像分类为合格(负类)或有缺陷(正类)。为了确保使用深度学习模型来优化此过程是值得的,假设公司需要每年至少赚取 $120,000 才能使此过程值得投资。我们还要考虑到,维护一个机器学习模型的成本是每月 $20,000。任何度量的阈值都需要与每月至少赚取 $30,000 的收益相关。
假设我们想使用基于 F 分数的度量。由于假阴性对总金额的影响比假阳性更大,我们可能想使用 F2 分数而不是 F1 分数。但是,使用相同度量分数的模型是否会在这两个度量中表现出不同的货币回报?这是理解的关键,以便可以设置适当的成功阈值。让我们尝试使用 Python 代码来分析分数行为:
-
首先,我们定义实际正例和实际负例的数量以及总数据量:
actual_positive = 95 total_data = 10000 actual_negative = total_data – actual_positive -
接下来,让我们定义方法来计算精准率、召回率、F1 分数和 F2 分数:
def precision(tp, fp): denominator = tp + fp if denominator == 0: return 0 return tp/ denominator def recall(tp, fn): denominator = tp + fn if denominator == 0: return 0 return tp/denominator def f1score(tp, fp, fn): prec = precision(tp, fp) rec = recall(tp, fn) denominator = prec+ rec if denominator == 0: return 0 return 2 * (prec * rec) / denominator def fbeta(tp, fp, fn, beta=0.5): prec = precision(tp, fp) rec = recall(tp, fn) denominator = beta**2 * prec + rec if denominator == 0: return 0 return (1+beta**2) * (prec * rec) / denominator -
接下来,让我们定义计算最终现金的方法:
def compute_total_cash(tp, fp, fn, tn): return tp * -50 + fp * -50 + fn * -1050 + tn * 150 -
让我们使用
compute_total_cash方法来计算当前设置下的基准现金,这时没有使用任何模型。这样我们可以找出一个模型被认为有足够价值用于使用的现金成功阈值:baseline_cash = compute_total_cash(0, 0, actual_positive, actual_negative) threshold_cash_line = baseline_cash + 30000 -
接下来,我们将模拟所有可能的真阳性(
tp)、假阳性(fp)、真阴性(tn)和假阴性(fn)组合,并计算 F1 和 F2 分数:f1_scores = [] f2_scores = [] total_cash = [] for tp in range(0, 96): fn = 95 - tp for fp in range(0, actual_negative): tn = total_data - tp - fp - fn f1_scores.append(f1score(tp, fp, fn)) f2_scores.append(fbeta(tp, fp, fn, beta=2.0)) total_cash.append(compute_total_cash(tp, fp, fn, tn) -
现在,让我们分别绘制这两个分数与总现金回报的关系,同时使用
threshold_cash_line和baseline_cash绘制水平线:import matplotlib.pyplot as plt fig, axs = plt.subplots(2, figsize=(18, 15)) axs[0].scatter(f1_scores, total_cash, alpha=0.01) axs[1].scatter(f2_scores, total_cash, alpha=0.01) for i in range(2): axs[i].axhline(y=baseline_cash, color='r', linestyle='dotted') axs[i].axhline(y=threshold_cash_line, color='y', linestyle='dashdot')这将产生以下图形:
.jpg)
.jpg)
图 10.1 – 现金与 F1 分数的关系,以及现金与 F2 分数的关系
- 图表表明,尽管 F2 分数对召回率进行了较高的加权,但在较低分数范围内波动很大。这里的目标是确保度量标准能与成功正确关联,因此,在相同分数下出现更大的现金波动并不是一个理想特征。F1 分数在这种情况下可能是更明智的选择。以最上方的水平线(它指的是我们需要达到的最低 30,000 现金阈值)为参考,我们想找到一个点,在 F1 分数图中,无法获得比阈值更低的分数。大致上,0.65 的 F1 分数应能保证模型产生一个有效分数。
本例展示了正确选择度量标准并找到可以直接与成功关联的阈值所需的分析层次,同时考虑到货币的盈利与亏损。然而,重要的是要注意,并非所有机器学习项目都能以美元成本来衡量。一些项目可能与现金无关,这完全是可以接受的。然而,要想取得成功,关键是以利益相关者能理解的方式量化模型的价值。如果没有人理解模型所带来的价值,那么项目不太可能成功。
接下来,让我们深入探讨在深度学习模型中直接优化度量标准的概念。
直接优化度量标准
用于训练深度学习模型的损失函数和度量标准是两个独立的组成部分。你可以用来提高模型准确性的技巧之一是直接针对度量标准进行优化,而不仅仅是监控性能,以选择表现最佳的模型权重并使用提前停止。换句话说,就是直接将度量标准作为损失函数使用!
通过直接优化感兴趣的度量标准,模型有机会以与最终目标相关的方式改进,而不是仅仅为了优化一个可能与模型的最终表现无直接关系的代理损失函数。这意味着,当将度量标准作为损失函数直接使用时,模型可能会取得更好的表现。
然而,并非所有度量标准都可以用作损失函数,因为并非所有度量标准都是可微的。记住,反向传播要求所使用的所有函数都是可微的,以便计算梯度来更新神经网络的权重。请注意,离散的函数并不都可微。以下是一些常见的不可微离散函数,以及用于快速识别的 NumPy 方法:
-
np.min,np.max,np.argmin,和np.argmax -
np.clip -
NumPy 中需要注意的其他函数包括
np.sign,np.piecewise,np.digitize,np.searchsorted,np.histogram,np.fft,np.count_nonzero,np.round,np.cumsum,和np.percentile
此外,使用度量作为损失函数有时可能导致次优性能,因为度量并不总是能够捕捉到模型需要学习的所有方面,这些方面对模型的良好表现至关重要。问题的一些重要方面可能很难直接度量或包含在度量中。这些方面可能构成模型学习的基础,模型必须先学习这些内容,才能逐渐在选定的度量上取得更好的表现。例如,在图像识别中,模型需要先学习识别更抽象的特征,如纹理、光照或视角,才能尝试在准确性上取得进展。如果这些特征没有在度量中得到体现,模型可能不会学会识别它们,导致次优的性能。一个好的解决方案是,初期可以先尝试更常见的损失函数,然后使用度量作为损失函数,或者结合原始损失函数和度量来微调模型。
虽然在某些情况下使用度量作为损失函数可能是有益的,但这并不是提升性能的万无一失的方法。这种方法的有效性在很大程度上依赖于具体的应用场景以及所处理问题的复杂性。通过这种方法获得的性能提升可能微乎其微,对于某些项目来说,甚至难以察觉。然而,当成功应用时,它可以带来性能的实质性提升。
总结
在本章中,我们简要概述了不同的模型评估方法,以及如何利用这些方法来衡量深度学习模型的性能。我们从所有介绍的方法中,首先探讨了度量工程的话题。我们介绍了常见的基本模型评估度量标准。基于此,我们讨论了使用基本模型评估度量标准的局限性,并引入了为特定问题量身定制模型评估度量标准的概念。我们还探讨了通过将度量作为损失函数直接进行优化的想法。虽然这种方法可以带来好处,但也需要考虑潜在的陷阱和局限性,以及这种方法适用于的特定使用场景。
深度学习模型的评估需要仔细考虑适当的评估方法、度量标准和统计检验。希望通过阅读本章内容,我能帮助你顺利进入度量工程的领域,鼓励你根据提供的指南迈出深入度量工程的第一步,并强调度量工程作为提升模型评估的重要组成部分的意义。
然而,无论最终选择的度量标准多么优秀和恰当,某些信息仍然可能隐藏在单一的度量值背后。在下一章,我们将介绍一种关键方法,帮助揭示神经网络模型在做出预测时隐藏的、无论是期望的还是不期望的行为。
第十一章:解释神经网络预测
你是否曾经想过,为什么一个面部识别系统将一张肤色较深的人的照片标记为假阳性,而正确识别肤色较浅的人?或者,为什么一辆自动驾驶汽车选择转向并造成了事故,而不是刹车并避开碰撞?这些问题展示了在关键应用场景下理解模型为何做出某个预测值的重要性。通过为模型的预测提供解释,我们可以深入了解模型如何工作以及为何做出特定决策,这对于透明度、问责性、信任度、符合法规的要求以及提高表现至关重要。
本章将探讨用于解释模型预测的神经网络特定方法。此外,我们还将讨论如何量化解释方法的质量。我们还将讨论模型解释的挑战和局限性,以及如何评估其有效性。
具体来说,将涵盖以下主题:
-
探索预测解释的价值
-
解密预测解释技术
-
探索基于梯度的预测解释
-
信任和理解集成梯度
-
使用集成梯度来帮助理解预测
-
自动解释预测
-
探索预测解释中的常见陷阱及如何避免它们
技术要求
本章包括一些 Python 编程语言中的实际实现。要完成它,你需要在计算机上安装以下库:
-
pandas -
captum -
transformers-interpret -
transformers -
pytorch -
numpy
代码文件可在 GitHub 上找到:github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_11。
探索预测解释的价值
首先,通过模型的预测来解释模型的概念被称为许多其他名称,包括可解释 AI、可信 AI、透明 AI、可解释机器学习、负责任的 AI 和伦理 AI。在这里,我们将这种范式称为预测解释,这是一个简洁且明确的说法。
预测解释并不是大多数机器学习从业者采用的技术。预测解释的价值在很大程度上取决于具体的应用场景。尽管有说法认为,解释能够提高透明度、问责性、信任度、符合法规的要求,以及提升模型表现,但并不是每个人都关心这些问题。与其了解其好处,我们不如从另一个角度来审视,探索一些常见因素,这些因素促使从业者采用预测解释,通常归因于以下条件:
预测解释技术为利用您构建的模型提供以下好处:
-
透明性:预测解释使得模型预测的消费者能够了解理由,这反过来会增强消费者对预测结果的信任。一个透明的模型使消费者能够客观评估其智能,从而增加信任度并促进采用。
-
问责制:预测解释使得消费者能够进行根本原因分析,这在关键使用案例或在决策过程中有人工干预、以模型预测作为参考的使用案例中尤其重要。
-
信任:没有信任,没人会使用模型的预测。预测解释有助于在建立更高信任的过程中提供一小步推动。
-
合规性要求:一些政府执行法律,要求在某些行业(如银行和保险)中,由计算机系统做出的决策必须可解释。
-
度量性能:进行根本原因分析的能力可以帮助更好地理解模型的行为或训练数据集。这反过来又能使机器学习从业者通过预测解释来改进或修复发现的问题,最终提高度量性能。
这份好处清单使得在任何使用案例和模型中使用预测解释都变得值得。然而,在某些条件下,使用预测解释的价值呈指数增长。当使用是基于特定目标时,该方法的效果将加倍。让我们进一步探讨一些促使从业者采用预测解释技术的常见条件:
-
关键和高影响力的使用案例:在这些使用案例中,模型的决策通常会对人类福利、安全或福祉产生重大后果。全面理解模型的行为有助于降低最坏情况的发生风险。这些后果可能从数十亿的损失到实际的人命丧失。
-
未达到成功所需的阈值:如果机器学习项目在模型开发阶段无法达到成功的阈值,那么它就无法进入机器学习生命周期中的模型部署阶段。理解模型对不同输入的行为可以帮助判断数据质量是否差,帮助发现数据是否存在促进过拟合的偏差模式,并且通常有助于调试如何改进模型的性能。
-
模型在最简单的例子中做出错误预测:在最复杂的例子中做出错误预测是可以预见的,尤其是当人类在做出相同决策时也可能犯错。然而,在最简单的例子中出现错误,说明模型没有学习到正确的内容。了解模型聚焦的内容可能是理解模型失败原因的关键。
-
监管法律要求在决策过程中对使用机器学习模型的责任进行追溯:这意味着预测解释将不再仅仅是机器学习从业人员理解模型如何在不同输入下行为的工具,而是在模型部署后使用,帮助使用这些预测结果的人们理解为何做出某个决策,从而确保责任可追溯。
-
模型在部署后未能做出正确预测:你是否曾考虑过,在交叉验证设置中,具有高准确性度量性能的好模型是否意味着模型关注的是正确的内容?即使使用交叉验证分区策略,数据仍然可能偏向某些特定的条件。这意味着,当模型在实际环境中部署时,它遇到的数据可能来自于与原始模型开发数据集不同的分布和条件。在这种情况下,预测解释可以帮助揭示模型在新数据或用于模型开发的数据中的偏差和不良行为。换句话说,你需要量化正确预测和错误预测之间的差异。
在你的使用案例和开发工作流中,不使用预测解释技术也是可以的。然而,当遇到这些极端情况时,预测解释技术将是你克服障碍的关键工具。但预测解释究竟解释了什么呢?在接下来的部分,我们将简要介绍预测解释方法,并深入探讨本章将介绍的、专门针对神经网络的预测解释方法。
解密预测解释技术
预测解释是一种尝试解释模型决策背后逻辑的技术,给定输入数据。一些机器学习模型被设计为开箱即用时就更具透明性和可解释性。例如,决策树模型就是一个从头开始构建的模型,使用显式的条件规则将数据划分成多个分区,最终得出特定的决策,从而可以通过预测数据样本时使用的显式规则来解释预测结果。然而,像神经网络这样的模型通常被视为黑箱,没有直接的方式来获取决策背后的原因。
一个模型对数据样本的决策逻辑可以通过多种方式进行解释和展示,只要它包含关于最终决策如何做出的信息。此外,机器学习模型做出的预测可以以模型无关或特定模型的方式进行解释。使用模型的预测可以进行几种类型的解释:
-
基于显著性(Saliency)的解释:这也被称为重要性归因、特征重要性或特征影响
-
透明模型逻辑基础的解释:提供为什么做出某个决策的理由
-
基于示例的解释:使用相似的数据推理为什么预测某个标签
神经网络作为一个“黑箱”,只能通过基于显著性(saliency)的方法或基于示例(exemplar)的方法进行解释。然而,已经发明了绕道方法,通过使用从神经网络到更透明和可解释模型(如线性模型)的知识蒸馏方法,来实现间接的透明模型逻辑解释。此外,Transformer 中实现的注意力机制提供了一种通过其注意力图检查特征重要性的浅层方法,但在以下几个方面有所不足:
-
注意力权重并非特定于某个特征,因为它们是通过计算所有输入标记与感兴趣输出标记之间的相互作用得到的。这意味着该权重并不代表对单一特征的真正依赖。
-
注意力机制关注的输入在网络的后续部分仍然无法使用。
-
注意力图可能存在偏差,并导致某些特征被忽视。
此外,应该优先选择基于神经网络的解释技术而非模型无关的解释技术,因为它们能够提供更详细和细致的解释,同时在计算需求和时间方面更为高效。在本章中,我们将专注于使用更可靠的方法进行神经网络的基于显著性的解释。
在接下来的章节中,我们将深入探讨神经网络模型特定解释背后的核心工作——基于梯度的显著性解释。
探索基于梯度的预测解释
当前最先进的神经网络基于解释技术是使用通过反向传播获得的梯度的变种。基于梯度的神经网络模型解释之所以有效,是因为它们依赖于神经网络中权重在训练过程中如何通过反向传播更新的基本原理。在反向传播过程中,计算网络中权重的损失函数的偏导数,从而得到损失函数相对于权重的梯度。
这个梯度为我们提供了输入数据对整体损失的贡献程度。记住,梯度衡量的是输入值相对于损失函数的敏感度。这意味着它提供了修改特定输入值时预测结果波动的程度,代表了输入数据的重要性。输入数据可以选择为神经网络的权重,或者是整个神经网络的实际输入。在大多数情况下,基于实际输入的解释已足以提供对重要特征组的清晰解释。然而,有时需要更精细的解释来解码高度归因且重要的实际输入数据的潜在特征。
例如,考虑使用卷积神经网络(CNN)根据图像数据识别动物品种的情况。如果一张狗站在草地上的图像,某个归因方法显示狗和草地都很重要,那么草地为什么重要呢?这可能是以下几个原因之一:
-
狗的毛发和草地是通过一个卷积滤波器识别的。
-
毛发和草地通过不同的滤波器被明确区分,但由于模型可能过拟合,认为狗必须伴随草地出现,因此它们也被视为同样重要。这可能意味着训练数据集只包含带有草地的狗的图像,暗示需要添加更多没有草地的狗的图像。
这将需要更深入地挖掘在激活的滤波器中与草地和狗相关的那些重要滤波器。即便确定了那些既重要又高度激活的滤波器,并且与草地和狗有关,仍然无法确切知道该滤波器识别的模式是什么。它是一个高级特征,比如狗的形状?还是毛发?在这种情况下,直接可视化这些模式是非常有益的。这个主题将在第十二章中详细讨论,解释 神经网络。
然而,梯度本身并不可靠,不能直接作为解释方法。更一般来说,纯梯度作为特征重要性解释方法的质量较低。然而,解释方法的质量并不是一个定量度量。生成的解释的感知质量是主观的,取决于消费者的需求。
假设某金融机构希望开发一个信用评分模型,以确定贷款批准。该机构希望使用一种解释技术来了解哪些特征对模型预测最为重要。银行可能更倾向于选择一种解释技术,重点解释那些对信用状况影响最大的特征,如信用历史、收入和未偿还的债务。这将帮助银行做出明智的贷款决策,并有效地管理风险。
另一方面,个别贷款申请人可能更倾向于获得一种更为全面的解释,解释应提供关于模型如何评估他们的具体财务状况的洞见,包括除了前三个最重要特征之外的因素,如他们的就业历史和近期的财务困境。这将帮助申请人做出明智的决策,以便在未来改善他们的信用状况。选择合适的解释技术非常重要,因为它应当针对受众及其特定需求,确保解释清晰且有助于实现其预定目的。
即便如此,仍然存在基于公理的评估方法,这些评估方法是基于一组被认为是理想的原则或公理来进行的。在所有技术中,一种名为集成梯度的技术因其专注于神经网络模型,并在开发过程中力求满足广泛接受的公理而脱颖而出。
接下来,我们将了解集成梯度如何解释神经网络预测,并理解该方法如何满足广泛接受的公理。
信任并理解集成梯度
首先,集成梯度技术在一些开源库中已经可以直接使用,例如shap和captum。我们可以直接利用这些库中的方法,而无需重新实现该方法。然而,如果在不了解技术细节的情况下使用该方法,可能会降低对解释结果的信任。如果你不信任该技术,那么解释结果本身几乎没有任何意义。因此,如果你不解释你的预测,预测结果本身也几乎没有任何意义!在这一部分,我们将深入探讨集成梯度的作用,以便你可以信任集成梯度所能解释的内容。
集成梯度在基于基本梯度的特征重要性解释方法中添加了一些额外的组成部分。这些组成部分旨在满足几个关键公理,这些公理决定了解释方法的可靠性。这些公理如下:
-
敏感性:对于两个数据样本,模型在这两个样本之间给出了两个不同的预测,而这两个样本唯一的不同之处在于一个特征应被赋予非零重要性分数。像 ReLU 这样的激活函数会打破这一公理,因为当输入值为零时,梯度也会被降至零,尽管两个预测之间的结果是不同的。
-
实现不变性:两个在所有可能数据上表现相同的模型,必须产生相同的重要性得分。假设任何外部数据以及在整个模型生命周期中接收到的数据都包括在内。
-
完整性:对于人类和机器学习的解释,提供反事实报告是制作高质量解释的最佳方法之一。为了评估模型的准确性表现,使用基线模型的效果相同。对于解释方法,这意味着构建一个可以产生中立预测的基线数据样本。完整性意味着每个特征列的重要性得分必须加起来等于基线样本与目标样本之间预测得分的差异。这个公理在一些使用场景中可能更有用,比如回归问题中,预测值直接作为输出,而不是在多类设置中,预测值仅用于选择最优类别。
-
线性:如果你线性地组合两个神经网络模型,即简单的加权相加,那么在数据样本上的组合神经网络的解释必须等于两个单独神经网络模型解释的加权相加。模型中实现的线性行为应当得到尊重。一个直接的公理专门设计用于一些方法产生无法解释的重要性值。
现在我们已经理解了可以用来比较方法的核心公理,值得注意的是,集成梯度满足所有这些公理。简而言之,集成梯度通过从一条直线路径(线性插值)中采样的样本来集成梯度,路径从选定的基线数据样本到目标数据样本,然后将其乘以目标样本和基线样本预测值之间的差异。梯度的路径积分就是梯度值沿直线路径下的曲线下面积。这个值表示输入特征值沿路径变化时,模型输出的变化。变化速率直接转化为特征重要性。通过将这个值与实际输入值和基线输入值之间的差异相乘,我们得到集成梯度中该特征对模型输出的贡献。图 11.1展示了这个过程,结果是通过在一个橙子图像上计算预训练的 ResNet50 模型预测的集成梯度。该模型能够预测橙子类别,并以 46%的概率预测该图像为橙子:

图 11.1 – 使用经过预训练的 ResNet50 模型计算集成梯度的示意图,该模型能够预测橙子类别的图像
更直观地说,这意味着集成梯度衡量的是当目标输入特征的值朝向中立基准样本变化时,模型预测的变化率,并提供了根据预测正确缩放的可解释的重要性:
特征重要性 = 预测差异 × 梯度路径曲线下的面积
这使得它能够捕捉模型输出对特征在整个值范围内变化的敏感度。集成梯度确保通过从基准样本到目标样本之间线性插值路径中的样本聚合梯度,确保每个特征的贡献与其对模型输出的影响成比例。使用普通梯度作为核心,集成梯度能够满足实现不变性,因为用于计算偏导数的链式法则使得梯度可以部分计算。
对于集成梯度,如果某一特征对模型输出的影响是另一特征的两倍,那么它的归因得分将是另一特征的两倍,因为该特征沿路径的梯度将是另一特征的两倍,从而满足线性关系。此外,由于重要性是通过基准输入和目标输入之间的预测差异来缩放的,集成梯度将满足完整性公理。这些是局部解释,通过个体数据样本提供预测结果的理由。作为附加优势,通过通过均值或中位数聚合所有数据样本的局部特征重要性,你可以获得模型的全局特征重要性。
接下来,我们将尝试使用集成梯度来解释模型的预测结果。
使用集成梯度来辅助理解预测结果
截至写作时,两个软件包提供了易于使用的类和方法来计算集成梯度,分别是captum和shap库。在本教程中,我们将使用captum库。captum库支持来自 TensorFlow 和 PyTorch 的模型。我们将在这里使用 PyTorch。在本教程中,我们将通过解释一个名为DeBERTA的 SoTA(最先进)变压器模型,来处理文本情感多分类任务。让我们一步一步地来了解这个用例:
-
首先,让我们导入必要的库和方法:
from transformers import ( DebertaForSequenceClassification, EvalPrediction, DebertaConfig, DebertaTokenizer, Trainer, TrainingArguments, IntervalStrategy, EarlyStoppingCallback ) import pandas as pd from sklearn.model_selection import train_test_split import torch -
接下来,我们将导入为本教程自定义的文本情感数据集:
df = pd.read_csv('text_sentiment_dataset.csv')该数据包含 100 行,三种情感类别(“中立”,“积极”,“消极”)的分布均衡。稍后我们将深入分析该数据集中的几个样本。
-
现在,我们将定义标签映射,用于将情感标签映射到数字 ID 以及反向映射:
label2id = {"negative": 0, "neutral": 1, "positive": 2} id2label = {v: k for k, v in label2id.items()} -
接下来,我们将创建一个方法,应用文本预处理,通过使用预训练的基于字节对编码的分词器对文本数据进行分词:
tokenizer = DebertaTokenizer.from_pretrained("microsoft/deberta-base") def preprocess_function(examples): inputs = tokenizer(examples["Text"].values.tolist(), padding="max_length", truncation=True) inputs["labels"] = [label2id[label] for label in examples["Sentiment"].values] return inputs -
为了训练一个模型,我们需要制定交叉验证策略,因此我们将在这里使用一个简单的训练和验证集划分:
train_df, test_df = train_test_split(df, test_size=0.2, random_state=42) -
由于我们将使用来自 Hugging Face Transformers 库的 PyTorch 模型,我们需要使用 PyTorch 数据集格式并拆分数据集以训练模型。在这里,我们将定义 PyTorch 数据集并初始化训练和验证数据集实例:
class TextClassificationDataset(torch.utils.data.Dataset): def __init__(self, examples): self.examples = examples def __getitem__(self, index): return {k: torch.tensor(v[index]) for k, v in self.examples.items()} def __len__(self): return len(self.examples["input_ids"])train_dataset = TextClassificationDataset(preprocess_function(train_df)) test_dataset = TextClassificationDataset(preprocess_function(test_df)) -
现在数据集已经预处理完成,准备好用于训练,让我们从 Hugging Face 加载我们随机初始化的 DeBERTA 模型:
deberta_config = { "model_type": "deberta-v2", "attention_probs_dropout_prob": 0.1, "hidden_act": "gelu", "hidden_dropout_prob": 0.1, "hidden_size": 768, "initializer_range": 0.02, "intermediate_size": 3072, "max_position_embeddings": 512, "relative_attention": True, "position_buckets": 256, "norm_rel_ebd": "layer_norm", "share_att_key": True, "pos_att_type": "p2c|c2p", "layer_norm_eps": 1e-7, "max_relative_positions": -1, "position_biased_input": False, "num_attention_heads": 12, "num_hidden_layers": 12, "type_vocab_size": 0, "vocab_size": 128100 } model_config = DebertaConfig(id2label=id2label, label2id=label2id, **deberta_v3_config) model = DebertaForSequenceClassification(model_config) -
由于这是一个多类别设置,我们将使用准确率度量,它将在每个周期计算,并与交叉熵损失一起使用,后者将用于训练模型:
def compute_metrics(p: EvalPrediction): preds = p.predictions[0] if isinstance(p.predictions, tuple) else p.predictions preds = np.argmax(preds, axis=1) return {"accuracy": (preds == p.label_ids).astype(np.float32).mean().item()} -
现在,我们将定义训练参数:
training_args = TrainingArguments( output_dir="./results", num_train_epochs=1000, per_device_train_batch_size=8, per_device_eval_batch_size=16, warmup_steps=100, weight_decay=0.01, logging_dir="./logs", logging_steps=10, evaluation_strategy=IntervalStrategy.EPOCH, save_strategy=IntervalStrategy.EPOCH, load_best_model_at_end=True, learning_rate=0.000025, save_total_limit=2, )这将把检查点保存到
results文件夹中,用 100 次热身迭代训练模型,在训练结束时加载最佳模型,并将模型学习率设置为非常小的 0.000025,这是正确收敛到解所必需的。 -
接下来,我们将初始化 Hugging Face 的
trainer实例,它将接收训练参数并利用这些参数执行实际的训练过程:trainer = Trainer( model=model, args=training_args, train_dataset=train_dataset, eval_dataset=test_dataset, compute_metrics=compute_metrics, callbacks = [EarlyStoppingCallback(early_stopping_patience=20)] )请注意,使用了 20 次迭代的提前停止。当模型在指定的迭代次数内没有在验证集上有所改进时,提前停止将停止模型的训练,同时进行 1,000 次训练周期。
-
最后,让我们进行训练并打印出最终的最佳模型评估分数!
trainer.train() trainer_v2.evaluate()这将产生如下输出:
{'eval_loss': 0.1446695625782013, 'eval_accuracy': 0.9523809552192688, 'eval_runtime': 19.497, 'eval_samples_per_second': 1.077, 'eval_steps_per_second': 0.103} -
95.23%的准确率对于一个多类别模型来说是一个相当不错的分数。你可能认为该模型已经足够好,能够部署,但它真的是表面看到的那么简单吗?让我们通过使用集成梯度的预测解释来调查模型的行为,看看它的表现如何。首先,让我们从
transformers_interpret库定义解释器实例:from transformers_interpret import SequenceClassificationExplainer cls_explainer = SequenceClassificationExplainer(model, tokenizer)transformers_interpret库在底层使用captum库,并实现了方法和类,使得基于 Hugging Face Transformers 库构建的模型可以轻松地解释和可视化文本重要性。在所有这些操作中,我们将重要性得分映射到颜色代码,并将令牌 ID 映射回实际的令牌字符串。 -
接下来,我们将解释来自验证集的两个样本,分别是负标签和正标签,这些样本恰好位于前面三个索引位置:
for idx in [0, 8, 6, 12]: text = test_df['Text'].iloc[idx] label = test_df['Sentiment'].iloc[idx] word_attributions = cls_explainer(text) cls_explainer.visualize()这将导致图 11.2中显示的输出:

图 11.2 – 基于 transformers_interpret 的集成梯度在验证数据集上的结果
绿色高亮的词表示积极的归因,而红色高亮的词表示对预测标签类别的负面归因。绿色越深,表示对预测标签类别的积极归因越强。红色越深,表示对预测标签类别的负面归因越强。
-
第一个和第二个示例显示了正确预测的积极情感句子。对于第一个示例,根据常识,amazing 是最应该对积极情感归因的词汇。对于第二个示例,happy 应该是对积极情感做出贡献的词汇。然而,在这两个示例中,词汇 bored 被错误地用作强烈的积极情感预测指标,这不是我们想要的行为。这表明数据集存在偏差,词汇 bored 可能在所有标记为积极情感的样本中都有出现。
-
第三个和第四个示例展示了正确预测的负面情感。在第三个示例中,句子 never going to end 和 keeps dragging on 应该是负面情感预测的重点。在第四个示例中,词汇 wrong 以及短语 has gone wrong 和 so frustrated 应该是负面情感预测的重点。然而,这两个示例都始终依赖于词汇 day 来进行负面情感预测。这表明数据集存在偏差,词汇 day 经常出现在标记为负面情感的样本中。
-
所有这些意味着,要么需要重新准备数据,要么需要添加更多的数据,以多样化词汇使用的分布,这样才能构建一个合适的模型。
本教程展示了利用预测解释可以获得的一种单一类型的好处。特别是,它展示了模型未能捕捉到可靠部署所需的行为,即便基于准确率的指标得分良好。
集成梯度技术是一种灵活的技术,可以应用于任何神经网络模型以及任何类型的输入变量类型。为了从解释结果中受益,你需要在业务目标的背景下得出有意义的见解。要从集成梯度的结果中得出有意义的见解和结论,运用常识和逻辑推理至关重要,正如在教程中手动演示的那样。然而,有一种方法可以帮助你获得有意义的见解,尤其是在数据量过大且变数太多无法手动解码时。我们将在下一节中深入讨论这个方法。
自动解释预测解释
一种从预测解释中得出洞见的有用方法就是使用大型语言模型(LLMs),例如 ChatGPT。ChatGPT 是一个训练用于提供与给定指令相关的逻辑推理结果的变换模型。理论上,如果你能将预测解释数据格式化,以便可以输入到变换模型中,并指示 LLM 从中获取洞见,你就能够从多个不同角度获得洞见。
在前面的教程中,我们尝试解释了四个不同样本的解释,这些样本包括两个正确预测的正面情感示例和两个正确预测的负面情感示例。现在,让我们使用 LLM 模型来获得一些洞见。这里,我们将分别为两个正确识别的正面情感和两个正确识别的负面情感示例生成洞见,因为与直接插入所有四个示例相比,这种方法往往能提供更合理的结果。我们使用了以下提示:
"I have built a sentiment classification model using HuggingFace defined classes, with 3 labels "neutral", "positive", "negative". I also utilized the integrated gradients method to obtain token attributions for the examples below: First Example with correctly predicted positive sentiment [('[CLS]', 0.0), ('I', 0.04537756915960333), ('was', 0.32506422578050986), ('hesitant', -0.011328562581410014), ('to', 0.2591512168119563), ('try', 0.07210600939837246), ('that', 0.1872696259797412), ('new', 0.25720454853718405), ('restaurant', 0.09906425532043227), (',', -0.08526821845533564), ('but', 0.26155394517459857), ('it', 0.029711535733103797), ('ended', 0.03380972261608883), ('up', 0.10830705227933887), ('being', 0.09771634151976362), ('amazing', 0.08913346633282519), ('and', 0.1429717885722044), ('I', 0.3408664658447834), ('was', 0.3248006076552883), ('far', 0.11463247647745932), ('from', 0.29795241654484333), ('bored', 0.4944594024849995), ('.', 0.10894853119482743), ('', 0.0376325816741505), ('[SEP]', 0.0)] Second example with correctly predicted positive sentiment [('[CLS]', 0.0), ('I', 0.00293608614786346), ("'m", 0.30945414416090317), ('happy', -0.028173800615734133), ('to', 0.31870826361945737), ('have', 0.15675763188868433), ('a', 0.07581024366375723), ('job', -0.020856788304256305), ('that', 0.20868353147138577), ('keeps', 0.2024940149527261), ('me', 0.046306584007035426), ('from', 0.41459097144711715), ('getting', 0.059558828031696716), ('bored', 0.7089862879353616), ('.', 0.0744570784079903), ('', -0.02775527287562979), ('[SEP]', 0.0)], Tell me anything wrong about this model to predict positive and negative attributions. Provide me a complete analysis and see if the most highly attributed words make sense for its predicted label. Judge the models based on the relative score and focus on the highest scored token individually and see whether it make sense."
请注意,单词令牌和归属元组的列表是前面教程中word_attributions变量的内容。对于这个提示,ChatGPT 模型返回了以下未修改的响应:
根据给定的示例,模型在预测正面情感方面表现良好,因为在两个案例中预测的标签都是正确的。然而,值得注意的是,词汇归属应谨慎解读,因为它们并不总是可靠的指标,无法准确反映句子中每个单词的重要性。
观察第一个示例的词汇归属,我们可以看到最高归属的词是“bored”,得分为 0.494。对于正面情感的句子来说,这可能看起来很奇怪,但模型可能高估了这个词的重要性,因为它是在否定一个最初存在的负面情感。模型也许已经学会了克服无聊是人们认为积极的事情。第二高归属的词是“I”和“was”,它们的得分都在 0.3 以上,这很合理,因为它们表明了写作者的个人经验和观点。其他高度归属的词,如“to”、“from”和“new”,也似乎在句子的语境中是合适的。
查看第二个例子的标记归属,我们可以看到归属分数最高的词是“bored”,得分为 0.709。这可能是因为模型对“这份工作让作者不至于感到无聊”这一事实给予了很高的权重,这被视为一件积极的事情。第二高的归属词是“from”,得分为 0.415,在句子的上下文中也是合理的,因为它表示这份工作让作者避免了做某事。其他高度归属的词语,如“to”和“that”,也似乎在 句子的上下文中有意义。
总体来说,虽然模型在预测积极情绪方面表现良好,但需要注意的是,标记归属可能是嘈杂的,应该谨慎解读。高度归属的词语在句子的上下文中似乎是有道理的,但重要的是要看整个句子,而不仅仅是单个词语,以便全面理解 情绪。
这个回应提供了对我们原始见解的不同看法,并提到一些怀疑的可能性并没有错。总体来说,这是一个不错的回应,它给我们提供了关于可能发生情况的不同视角,并表明大语言模型(LLM)可以用来解释预测结果的解释,作为一种辅助工具。现在,让我们用以下列表中的两个例子来替换归属结果,这些例子正确地识别为负面情绪:
Third example with correctly predicted negative sentiment
[('[CLS]', 0.0), ('It', 0.1781266370024602), ('feels', -0.08200280620716707), ('like', -0.03204718164120957), ('this', -0.05573060889100964), ('day', 0.6176120940811932), ('is', -0.0032201843949800626), ('never', 0.20518569446424909), ('going', 0.07198217726867306), ('to', 0.45876287617527467), ('end', -0.2538220352877727), ('.', -0.01766876291225962), ('Everything', 0.2927810551003227), ('just', 0.27789343264100713), ('keeps', 0.25860811147952395), ('dragging', 0.013653081036146358), ('on', 0.12974700461873728), ('.', -0.01804386178839869), ('', 0.07940230083266143), ('[SEP]', 0.0)]
Fourth example with correctly predicted negative sentiment [('[CLS]', 0.0), ('Everything', 0.051921060773658365), ('that', 0.1853413631332932), ('could', 0.16644341050771988), ('have', 0.09960723832267326), ('gone', 0.21400474006976702), ('wrong', 0.26952621495013096), (',', -0.02550525756341266), ('has', 0.46511206263887517), ('gone', 0.21385297386310367), ('wrong', 0.2691861963676513), ('this', 0.08573245861889027), ('day', 0.5967441822322336), ('.', 0.00910580036050777), ('I', 0.16215574709799985), ("'m", -0.05665909043004983), ('so', 0.20818789342264885), ('frustrated', 0.11472844834437185), ('.', 0.00924431805079032), ('', 0.1485924275199782), ('[SEP]', 0.0)]
ChatGPT 模型提供了以下回应:
从提供的例子来看,模型似乎在这两种情况下都正确地预测了负面情绪。然而,有几个方面可以分析,以确定模型是否表现良好 或不良。
首先,集成梯度方法提供的标记归属指示了每个标记对模型最终预测的贡献。在这两个例子中,归属分数最高的标记是“day”和“has gone wrong”,这似乎是有道理的,因为它们表明了 负面情绪。
然而,提供的标记归属存在一个问题,即某些标记的归属分数为负,这意味着它们对最终预测产生了负面影响。例如,在第一个例子中,标记“feels”具有负归属分数,但可以认为它实际上表明了负面情绪。类似地,在第二个例子中,标记“'m”具有负归属分数,但它是“I am”的缩写,表示 负面情绪。
另一个需要考虑的因素是最高归因令牌的相对得分。在第一个例子中,令牌“day”的归因得分显著高于其他任何令牌,这表明它在此案例中是预测负面情绪的最重要特征。然而,在第二个例子中,存在多个归因得分相似较高的令牌,例如“has gone wrong”和“frustrated”,这表明它们在预测负面情绪时是同样重要的特征。
总体而言,基于提供的例子和令牌归因,模型在预测负面情绪方面表现得相当不错。然而,令牌归因的分析表明,集成梯度方法可能在将得分归因于某些令牌时存在一些问题。还需要考虑最高归因令牌的相对得分和可解释性,以确定它们在预测标签的上下文中是否合理。
ChatGPT 的两个回答似乎都表示这种行为是有效的。由于大多数神经网络被配置为非线性模型,并具有许多非线性激活,记住该模型能够学习令牌之间的复杂交互。这意味着令牌通常是通过一种复杂的方式被模型感知的,这可能使它们彼此依赖。一个在原始上下文中具有高归因的令牌,在应用到另一个上下文时,可能不会产生相同的归因结果,因为多个令牌可能会相互依赖,作为产生强归因的前提。此外,令牌并不局限于与直接相邻的令牌进行交互,它们可以出现在文本行的任何位置。验证令牌是否过拟合的一种方法是通过预测仅包含day令牌的文本数据,看看它是否会预测为负面情绪,并通过预测包含bored令牌的文本数据,看看它是否会预测为正面情绪:
from transformers import TextClassificationPipeline
full_model = TextClassificationPipeline(model=model, tokenizer=tokenizer)
print(full_model("day"))
print(full_model("day day"))
print(full_model("day day day"))
print(full_model("day day day day"))
print(full_model("bored"))
print(full_model("bored bored"))
print(full_model("bored bored bored"))
print(full_model("bored bored bored bored"))
这将返回以下响应:
[{'label': 'neutral', 'score': 0.9857920408248901}]
[{'label': 'negative', 'score': 0.5286906361579895}]
[{'label': 'negative', 'score': 0.9102224111557007}]
[{'label': 'negative', 'score': 0.9497435688972473}]
[{'label': 'neutral', 'score': 0.9852404594421387}]
[{'label': 'positive', 'score': 0.8067558407783508}]
[{'label': 'positive', 'score': 0.974130392074585}]
[{'label': 'positive', 'score': 0.9776705503463745}]
有趣的是,单独使用day和bored令牌时,预测结果是中性情绪。你添加更多重复令牌时,预测结果将分别偏向负面和正面情绪。这证明模型确实对这些特定的词存在偏见,并且没有以正确的方式使用这些词,正如 ChatGPT 所说。请注意,这很可能是 ChatGPT 对你的案例做出的预测。
本教程展示了基于文本的神经网络的解释。然而,它也可以类似地应用于其他数据类型,如数值数据、分类数据或任何可以可靠地表示为文本格式的数据。特别是对于基于图像的预测解释,您可以使用像 OpenAI 的 GPT-4 这样的模型,它同时接受图像数据和文本数据。您还可以考虑使用transformers_interpret库中的可视化图像,并将其输入到 GPT-4 中!
接下来,我们将深入探讨在尝试解释您的预测和建议时常见的陷阱,并提供避免这些陷阱的建议。
探索预测解释中的常见陷阱以及如何避免它们
尽管预测解释已被证明是理解 AI 模型的宝贵工具,但有几个常见的陷阱可能会妨碍其效果。在本节中,我们将讨论这些陷阱,并提供避免它们的策略,确保预测解释继续成为理解和改进 AI 模型的有价值资源。以下是一些常见的陷阱及其解决方案:
-
过度依赖解释:尽管预测解释可以为模型的决策过程提供宝贵的见解,但过度依赖这些解释可能导致错误的结论。重要的是要记住,预测解释只是拼图中的一块,应该与其他评估方法结合使用,以全面了解模型的表现。这里的解决方案是使用多种评估方法的组合,包括性能指标、交叉验证和专家领域知识,来分析和验证模型的表现。
-
误解解释结果:解释预测结果可能是具有挑战性的,尤其是当处理复杂模型和大型数据集时。误解这些结果可能会导致关于模型行为和性能的错误结论。这里的解决方案是与领域专家合作,帮助解释结果,并确保从这些解释中得出的结论与现实世界的知识和预期一致。
-
忽视模型的局限性:预测解释能够提供关于模型决策过程的宝贵见解,但它们无法解决模型本身的固有局限性。承认并解决这些局限性是至关重要的,以确保模型的最佳性能。这里的解决方案是进行彻底的模型评估,识别并解决任何局限性,如过拟合、欠拟合或偏倚的训练数据。不断地重新评估并根据需要更新模型,以维持其最佳性能。
-
针对错误受众的解释:不同的利益相关者可能需要根据其专业知识和需求提供不同类型的解释。为目标受众提供过于技术化或过于简化的解释,会妨碍他们对预测的理解和使用。解决方案是根据目标受众的需求和专业知识量身定制解释。有时,需要对模型进行更为整体的解释,而不是逐个预测的解释,这种情况下可以考虑使用神经网络解释技术和聚合评估指标。对于非技术用户,仅通过提供原始特征重要性来解释预测是不够的,还需要通过自然语言解释从特征重要性中获得的见解,如自动解释预测解释部分所介绍的那样。对于有技术意识的用户,预测解释是合适的。
通过意识到这些常见的陷阱并实施避免策略,从业人员可以确保预测解释仍然是理解和改进人工智能模型的重要工具。通过将预测解释与其他评估方法结合,并与领域专家合作,可以全面了解模型的性能、行为和局限性,从而最终获得更准确、可靠的人工智能系统,更好地服务于其预期目标。
总结
在本章中,我们对预测解释的全貌进行了广泛的了解,深入探讨了集成梯度技术,实际应用于一个案例,并尝试通过 LLMs 手动和自动地解释集成梯度结果。我们还讨论了预测解释中的常见陷阱,并提供了避免这些陷阱的策略,确保这些解释在理解和改进人工智能模型中的有效性。
集成梯度是一种有用的技术和工具,可以为你的神经网络做出预测提供一种基于显著性的解释。通过预测解释来理解模型的过程提供了许多好处,这些好处可以帮助实现成功的机器学习项目和计划所需的标准。即使一切顺利,且机器学习应用场景并不关键,通过任何预测解释技术揭示你可能部署的模型行为,也可以帮助你改进模型的使用方式。
然而,基于显著性的解释只能帮助你了解哪些输入数据或输入神经元是重要的。但是,一个神经元从输入数据中能够检测到什么模式仍然是未知的。在下一章,我们将扩展这一方向,揭示理解神经元检测到的具体内容的技术。
通过意识到常见的陷阱并实施避免这些陷阱的策略,从业人员可以确保预测解释继续作为理解和改进 AI 模型的有价值工具。将预测解释与其他评估方法结合,并与领域专家合作,可以创造出更加准确可靠的 AI 系统,更好地服务于其预定目的。
在我们结束之前,最后要说的是,确保抽出时间通过预测解释技术(如积分梯度)来理解你的神经网络模型,并考虑潜在的陷阱,以最大化其效果!
进一步阅读
- Mukund Sundararajan, Ankur Taly, Qiqi Yan. 深度网络的公理化归因,载于:国际机器学习会议,2017. 网址:
arxiv.org/abs/1703.01365v2。
第十二章:解读神经网络
在尝试理解模型预测背后的原因时,局部单样本特征重要性可以是一个有价值的工具。此方法使你能够将分析重点集中在输入数据的较小部分,从而更有针对性地理解对模型输出起到重要作用的关键特征。然而,通常仍然不清楚模型使用了哪些模式来识别高度重要的特征。通过回顾来自目标样本的更多预测解释,战略性地辨别预测的实际原因,某种程度上可以规避这一问题,实际操作部分将在本章稍后介绍。然而,这种方法受到必须验证的样本数量的限制,有时仍然很难明确指出具体使用的模式。
深度神经网络(DNNs)学习低层到高层特征,帮助预测层在幕后辨识正确的标签。当我们在输入数据上使用基于局部特征重要性的解释时,我们无法确定到底是哪些低、中或高层的模式对输入数据的特征重要性做出了贡献。对于图像,这些特征从低层的简单形状到中层的人的轮廓形状,再到逐步组合成一个人的面孔或日常物品的模式。对于文本,这些特征从低层的词嵌入(表示单词的意义)到中层的语义角色(如句子中的单词角色,帮助正确表示文本意义的句子嵌入),一直到我们更熟悉的高层特征,如主题和情感。当然,这些只是我们对神经网络学习内容的理论假设。
本章将探讨一种方法,帮助清除深度神经网络学习特征中的所有模糊性,即通过输入优化直接可视化神经网络所检测到的模式。通过将直接学习的模式与激活过滤相结合进行可视化,我们可以揭示深度神经网络做出预测的实际原因。具体来说,本章将讨论以下主题:
-
解读神经元
-
寻找可解读的神经元
-
解读学习到的图像模式
-
发现反事实解释策略
技术要求
本章包括在 Python 编程语言中的实际实现。为完成本章,你需要一台已安装以下库的计算机:
-
torchvision -
torch -
torch-lucent==0.1.8 -
matplotlib==3.3.0 -
captum -
pillow -
numpy
代码文件可以在 GitHub 上找到:github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_12。
解释神经元
神经网络层中的神经元产生的特征将被后续层使用。这些产生的特征或激活值仅仅是学习到的模式在输入数据中显著性的一个指示。但你是否曾经想过这些模式是什么?解码神经网络实际学习到的模式可以进一步提高透明度,从而帮助实现第十一章中探索预测解释的价值部分提到的目标,解释神经网络预测。
数据由许多复杂的模式组合成单一样本。传统上,为了辨识神经元在检测什么,必须评估大量输入数据并与其他数据进行比较,以便由人类得出定性的结论,这既耗时又很难做到准确。该方法使我们能够直观地 pinpoint 产生高激活值的实际模式,而不受其他高度相关模式的干扰。
更正式地说,通过优化进行的特征可视化在以下用例中是有用的:
-
理解与混淆标签相关的模式,而无需领域专家的帮助:
-
这种情况在真实世界的音频数据中更为常见,在这些数据中,标签的声音常常与大量噪声混合在一起
-
这也可能发生在图像数据中
-
-
直接获取真实数据来测试关于神经网络学习内容的任何假设并非易事,且无法通过基于梯度的特征归因技术在可用数据上得到证明。
神经元解释技术的核心是神经输入优化,这是一种修改神经网络输入数据的过程,目的是使其在选择的神经元上产生高度激活。记住,在训练过程中,我们优化神经网络的权重以减少损失值。在这种技术中,我们随机初始化一个输入并优化该输入数据,使其在选定的神经元上产生高度激活,实际上将输入数据视为神经网络的权重。梯度可以自然地计算到输入数据阶段,使得在应用学习率后,可以根据计算的梯度更新输入数据。该技术还允许你联合优化多个神经元,使它们同时高度激活,并获得一张展示两种不同神经元如何共存的图像。
图 12.1展示了efficientnet-b0模型中从低级到中级和高级模式的概念:

图 12.1 – 来自 efficientnet-b0 模型的随机滤波器优化图像示例
如果你观察高层次的滤波器模式,第一个在随机滤波器上的优化图像看起来有点像花朵,第二个图像则像叶子图案。
然而,这种技术的一个主要警告是,得到的优化输入数据可能并不能代表与神经元相关的模式的所有真实变化。即使是像图像这样的动态输入数据变量,也可以被优化以多种方式展示模式,但最终的优化输入仍然可能会遗漏一些模式的表现。解决这个警告的一个好方法是,首先获得初始的优化输入数据变体,然后执行后续优化,并确保优化后的输入数据与初始变体不同。这可以通过联合优化一个额外的组件——即初始优化输入数据与当前优化输入数据之间的负余弦相似度来实现。这一技术有助于生成多样化的输入数据示例。但在你能够优化输入数据并尝试解释神经元之前,你需要一个策略来选择最佳的神经元进行输入数据优化,这将在下一节中讨论。
查找需要解释的神经元
在当今的最先进架构中,拥有数百万甚至数十亿个神经元,解释每一个神经元是不可行的,坦率来说,这也是浪费时间。选择要解释的神经元应根据你的目标来决定。以下列表展示了一些不同的目标和选择合适神经元的相关方法:
-
找出某个预测标签或类别模式的样子:在这种情况下,你应该简单地选择一个特定的神经元,用于预测目标标签或类别。这通常是为了理解模型是否很好地捕捉了该类别的模式,或者它是否学习到了无关的特征。在多标签场景中,多个标签总是一起存在,你希望解耦这些标签,从而更好地理解与单一标签相关的输入模式,这时这也会很有用。
-
想要理解在数据集中为什么某个特定标签可以被预测,或者一般而言所有标签的潜在原因:在这种情况下,你应该从全局神经元重要性评分中选择潜在中间层中最具影响力的神经元。全局神经元重要性可以通过聚合集成梯度方法的结果来获得,该方法应用于你验证数据集中的所有神经元(此方法在第十一章,解释神经网络预测中有介绍)。然后可以对所有神经元的影响值进行排名,并挑选出最重要的神经元。
-
找出基于显著性解释技术的预测原因的细分理由:在这种情况下,您应选择具有最高激活值和最高重要性得分的神经元。高度激活的神经元并不一定意味着它对某种预测很重要。此外,重要的神经元并不意味着该神经元被激活。利用整合梯度的重要性值和激活值来获取最重要的神经元将有助于确保选择您关心的神经元。此外,如果基于初始输入数据显著性地图有一个关注区域,您可以进一步通过仅选择影响所选关注区域的神经元来过滤出更多神经元。
-
理解多个标签或类别之间的相互作用:在关注多个标签或类别之间的关系重要的情况下,您可以选择捕捉这些相互作用的神经元。识别在预测多个标签或类别时激活度高且重要性得分高的神经元。分析这些神经元可以帮助您理解模型如何捕捉不同标签或类别之间的关系,并可能揭示改进的潜在领域。
-
研究模型对抗性攻击的稳健性:在这种情况下,您应选择对输入数据的对抗性扰动敏感的神经元。您可以生成对抗性示例,有关如何操作请参见第十四章,分析对抗性能,然后使用整合梯度等技术计算神经元的重要性得分。通过可视化受对抗性扰动影响最大的神经元,您可以深入了解模型的脆弱性并探索潜在的防御措施。
-
探索学习特征的分层结构:在这种情况下,您应选择来自 NN 不同层的神经元,以理解模型如何学习分层特征。选择早期层的神经元以研究低级特征,选择更深层次的神经元以研究高级特征。您还可以选择多个神经元以协同优化输入数据以获得高激活,以了解同一输入数据中多神经元学习模式的存在方式。可视化这些神经元可以帮助您理解模型对数据的内部表示及其如何构建越来越复杂的特征。这可以提供关于模型学习过程及其改进潜力领域的见解。
-
分析模型在不同数据集上的泛化能力:为了了解模型在新数据上的泛化能力,应该选择在不同数据集上始终重要的神经元。使用集成梯度等技术计算不同数据集上的神经元重要性得分,并识别在所有数据集上保持高重要性得分的神经元。通过可视化这些神经元,你可以深入了解模型的泛化能力,并发现潜在的改进领域。
现在我们已经建立了选择神经元进行解释的方法,让我们从实际的角度开始探索如何用图像输入数据解释神经元!
解释学习到的图像模式
解释接收图像数据的神经网络(NN)开启了解释的新范式,即可视化神经元正在检测的内容。以音频输入数据为例,解释神经网络可以使我们听到神经元检测到的内容,类似于我们在图像数据中可视化模式的方式!根据你的目标选择你想理解的神经元,并通过对图像数据的迭代优化,激活该神经元以可视化它检测到的模式。
然而,实际上,基于神经元优化图像数据时,存在一个问题,即生成的图像通常会产生被认为是噪声、不可解释且不美观的高频模式。高频模式被定义为那些强度较高且变化迅速的像素。这主要是由于像素可以表示的值范围通常没有约束,而且单独的像素并不是我们关心的语义单元。放大生成的图像可能会使其更具可解释性,但解释效果会因为需要进行人工评估和额外工作而降低。
这个问题可以通过以下技术有效缓解:
-
频率惩罚 – 示例技术如下:
-
在优化过程中随机使用双边滤波器对图像进行模糊处理,这样做的好处是还能保留边缘模式
-
在优化过程中保守地惩罚相邻像素之间的变化
-
-
图像增强
-
图像预处理 – 示例技术如下:
-
数据去相关
-
快速傅里叶变换
-
让我们通过使用在 ImageNet 数据集上预训练的 121 层 densenet 模型,继续我们的学习之旅。
使用图像输入数据和集成梯度来解释预测结果
在本节中,我们将探讨预测,并通过一个实际教程来解释如何解释 CNN 模型的预测,该模型接受带有集成梯度的图像输入数据,并提供一些对模型做出预测的原因的见解。在本教程中,我们将发现预测解释中缺失的必要答案,这将为我们理解 CNN 模型提供帮助。我们将按以下步骤进行:
-
本教程中,我们将使用
lucent库,它提供了通过优化特征可视化来解释神经网络的方法。此外,我们还将使用torch库来处理 densenet 模型。我们还将使用captum库来使用集成梯度方法。让我们从导入所有必要的库开始:import glob import numpy as np import torch import torch.nn as nn import torchvision.transforms as transforms from PIL import Image from captum.attr import IntegratedGradients from captum.attr import NoiseTunnel from captum.attr import visualization as viz from lucent.optvis import render, param, objectives from lucent.optvis.objectives import diversity -
接下来,我们将定义一个预训练的 densenet 模型的类:
class CNN(nn.Module): def __init__(self, num_classes, model='resnet50'): super(CNN, self).__init__() self.num_classes = num_classes self.chosen_model = model if self.chosen_model=='densenet121': self.model = models.densenet121(pretrained=True) self.classifier = nn.Sequential( nn.Dropout(p=0.1), nn.Linear(self.model.classifier.in_features, 256, bias=False), nn.ReLU(), nn.BatchNorm1d(256), nn.Linear(256, 128, bias=False), nn.ReLU(), nn.BatchNorm1d(128), nn.Linear(128, self.num_classes, bias=False), nn.BatchNorm1d(self.num_classes), ) self.model.classifier = self.classifier model_parameters = filter(lambda p: p.requires_grad, self.model.parameters()) params = sum([np.prod(p.size()) for p in model_parameters]) def forward(self, x): return self.model(x) -
我们将使用定义好的模型类加载一个名为
HAM10000的图像数据集上预训练的权重,该数据集包含七种不同的皮肤病变类别:checkpoint = torch.load('0.8228 checkpoint.pt', map_location=torch.device('cpu')) model = checkpoint['model'] -
预训练模型的预测层的第七个索引被训练用于预测黑色素瘤,这是一种皮肤癌。让我们看看
ISIC-2017数据集中的一些黑色素瘤示例,并看看当预测这些图像时,预训练模型到底关注了什么。我们将使用captum库中的集成梯度方法。首先,让我们定义需要的预处理步骤,以便进行模型推理,这将把numpy图像数组转换为torch张量,调整图像大小为预训练的图像尺寸,并进行归一化:resize_transform = transforms.Compose([ transforms.ToTensor(), transforms.Resize((224, 224)), ]) norm_transform = transforms.Normalize( mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])均值和标准差值直接来自 ImageNet 数据集,并用于预训练模型,因为在
HAM10000数据集上预训练之前,它已在 ImageNet 上进行过预训练。此外,224的维度也来自于 ImageNet 预训练设置。由于我们需要在调整大小后的中间结果,因此我们单独定义了调整大小和归一化的逻辑。 -
接下来,我们将使用
glob库加载提供的数据集文件夹中的所有黑色素瘤图像:all_melanoma_images = glob.glob("lesions_augmented_data/Data/test/Melanoma/*.jpg") -
我们将使用
captum库实现的集成梯度和噪声隧道方法,以平滑结果中的归因噪声。让我们定义执行这些组件所需的实例,并定义我们感兴趣的黑色素瘤目标类的预测类索引:integrated_gradients = IntegratedGradients(model) noise_tunnel_applyer = NoiseTunnel(integrated_gradients) prediction_label_index = 6 -
现在我们可以遍历前六张图像,应用预处理,使用
captum中的集成梯度方法,并最终可视化原始图像和获得的输入重要性热力图:for melanoma_image in all_melanoma_images[:6]: pil_image = Image.open(melanoma_image) img = np.array(pil_image) transformed_image = resize_transform(img) input = norm_transform(transformed_image).unsqueeze(0) attributions_ig_nt = noise_tunnel_applyer.attribute( input, nt_samples=10, nt_type='smoothgrad_sq', target= prediction_label_index) _ = viz.visualize_image_attr_multiple( np.transpose(attributions_ig_nt.squeeze().cpu().detach().numpy(), (1,2,0)), np.transpose(transformed_image.squeeze().cpu().detach().numpy(), (1,2,0)), ["original_image", "heat_map"], ["all", "positive"], cmap='turbo', show_colorbar=True)这将展示在图 12.2中呈现的可视化结果:

图 12.2 – 来自 ISIC-2017 数据集的六张真实黑色素瘤图像,以及模型的基于梯度的归因
在前五个例子中,模型似乎主要集中在较暗的斑点上,但它仍然会考虑周围的皮肤,尽管关注度较低。这可能表明模型在一定程度上依赖周围皮肤来预测黑色素瘤。但这也引发了一个问题:模型是在根据皮肤的暗度来预测黑色素瘤,还是在识别某种模式,还是两者兼有?在最后一个例子中,模型似乎没有特别集中,且并没有真正关注那些暗斑。这可能意味着皮肤有一些与黑色素瘤相关的模式,这些模式未必是颜色较深的。还有一些问题通过这些例子无法真正解答,具体如下:
-
这个模型是否依赖皮肤颜色来预测黑色素瘤?还是说它实际上是通过模式来预测的?
-
模型究竟检测到的模式是什么?
为了回答这些问题,我们将使用lucent库来可视化模型学到的模式,从而自信地预测黑色素瘤。
实际可视化带有图像输入数据的神经元
在本节中,我们将继续前面的教程,进一步探讨如何使用优化技术实际可视化带有图像输入数据的神经元,以获得对 CNN 模型所学模式和行为的深入理解。这个过程涉及选择要解释的神经元,为这些神经元优化图像数据,并应用正则化技术来生成可视化的可解释模式。通过可视化模型所学到的模式,我们可以更好地理解模型的预测,并回答传统特征重要性方法无法揭示的问题。
通过遵循本教程中概述的步骤,你可以可视化你的深度神经网络所学到的模式,从而更深入地理解模型的预测及其对这些预测有贡献的特征。这有助于回答关于模型是否依赖于某些特征的问题,比如皮肤颜色或黑色素瘤的形状,并提供关于模型模式和行为的宝贵见解。让我们开始吧:
-
首先,让我们定义必要的变量。我们希望可视化
Melanoma类别的图像模式,它位于第六个预测层索引处,因此我们必须按如下方式定义我们要优化的参数:param_to_optimize = "classifier_8:6" -
接下来,在第一次迭代中,我们将使用
lucent,具体来说,CPPN 由多个卷积层组成,激活函数是由元素级的正切、平方、除法和连接操作构成的组合函数。这意味着我们不是直接优化图像,而是优化生成主网络输入图像的 CPPN 卷积网络的参数。反向传播可以一直执行到生成的输入图像的第一层 CPPN 网络。初始输入图像是一个固定图像,包含图像中心的圆形区域,圆心的值接近零,并且逐渐增加到圆边缘。然而,使用 CPPN 时,学习率通常需要较低才能正常收敛。让我们定义一个大小为224的 CPPN 配置,并使用学习率较低的Adam优化器:cppn_param_f = lambda: param.cppn(224) cppn_opt = lambda params: torch.optim.Adam(params, 4e-3) -
最后,让我们利用定义的变量,使用 GPU 配置的模型可视化预测层中捕获的黑色素瘤部分的模式:
model.cuda() images = render.render_vis( model, param_to_optimize, cppn_param_f, cppn_opt, thresholds=np.linspace(0, 10000, 100, dtype=int).tolist(), verbose=True, show_inline=True )thresholds列表组件控制优化步骤的数量,并可视化优化图像的中间步骤号。此外,render_vis方法中内置的一个隐藏组件是transforms组件。transforms组件添加了最小化的增强,例如填充、抖动、随机缩放和随机旋转,以减少优化图像中的随机噪声。之前代码的结果如 图 12.3 所示:

图 12.3 – 优化 CPPN 的进展,以生成在黑色素瘤类别预测中具有高激活度的图像
从这个过程中生成了一个相当好的图像,看起来像是实际的黑色素瘤。
-
让我们来找出这张图像的黑色素瘤概率。我们可以通过定义进行推断所需的预处理方法来实现这一点:
inference_transform = transforms.Compose([ transforms.ToTensor(), transforms.Resize((224, 224)), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) -
在这里,我们预测从过程生成的最终优化图像的皮肤病变类别,同时禁用梯度计算:
def inference_lesion_model(img): transformed_image = inference_transform(img).unsqueeze(0) with torch.no_grad(): output = torch.nn.functional.softmax(model(transformed_image), dim=1) return output output = inference_lesion_model(images[-1].squeeze())结果显示图像被预测为黑色素瘤的概率为 100%!
然而,仅凭图像并不能明显表明所有不同类型的图像集可以被识别为黑色素瘤。有些类别可以在单张图像中充分表示,但有些标签确实无法在单张图像中表现出来。以背景类为例:不可能将每一个背景图像都放在一张图像中。以下是一些基于结果可能有用的问题:
-
皮肤的颜色是否重要?
-
黑色素瘤的形状是否重要,因为最终生成的图像似乎具有相似的黑色素瘤模式?
-
黑色素瘤斑块的颜色重要吗?这里有一些绿色斑块,它们的图案与红色斑块相似。
-
-
这里提到的用来确保多样性的损失函数可以提供更多的洞察。现在,让我们利用多样性目标与原始黑色素瘤预测层的目标一起,同时优化四张输入图像。批量模式功能不支持
lucent中的 CPPN,只支持基本输入图像初始化的param模块:obj = objectives.channel("classifier_8", 6) - 1e2 * diversity("input") batch_param_f = lambda: param.image(224, fft=True, decorrelate=True, batch=4) batch_images = render.render_vis( model, obj, batch_param_f, thresholds=np.linspace(0, 500, 50, dtype=int).tolist(), show_inline=True, verbose=True, )关于图像初始化方法的两个额外要点如下:
-
fft代表快速傅里叶变换 -
decorrelate对图像输入应用了 SVD
这两种技术在研究中得到认可,能够加速收敛,减少高频图像,并生成更美观的图像。
结果如图 12.4所示:
-

图 12.4 – 四张共同优化的图像,能够在黑色素瘤神经元上强烈激活
这些图像看起来非常独特。非黑色部分可能模拟了皮肤。我们来看一下每张图像对应黑色素瘤类别的概率,来验证一下:
outputs = []
for img_idx in range(4):
img = batch_images[-1][img_idx]
output = inference_lesion_model(img)
outputs.append((output[0][6], output.argmax()))
这将产生如下的数组:
[(tensor(0.9946, device='cuda:0'), tensor(6, device='cuda:0')),
(tensor(0.9899, device='cuda:0'), tensor(6, device='cuda:0')),
(tensor(0.7015, device='cuda:0'), tensor(6, device='cuda:0')),
(tensor(0.9939, device='cuda:0'), tensor(6, device='cuda:0'))]
它们的黑色素瘤概率都很高!由此可以得出以下结论:
-
模型在检测黑色素瘤时不太依赖皮肤的颜色。皮肤颜色所能提供的最大增益可能只是大约 3% 的概率提升。
-
模型主要依赖于底层低级模式来检测黑色素瘤。
-
模型对黑色素瘤斑块的颜色不太依赖。第一张生成图像和真实图像中的黑色素瘤颜色偏红,而批量生成图像中的黑色素瘤斑块颜色是黑色的。
-
模型能够从使用的真实图像中检测到较小的黑色素瘤信号。
这里的结果展示了每种洞察技术之间的互补性。我们将通过一些有用的笔记来结束这个话题,讲解关于神经元通过优化技术进行模式可视化的一般情况:
-
一些问题比其他问题更难收敛,有些问题根本无法收敛。准备好尝试多种设置,看看能否得到一个能够在你选择的神经元、通道或整个层上强烈激活的输入。你甚至可以选择多个神经元,看看它们如何相互作用!
-
损失函数可能会变得非常负,且输入收敛的越多,损失越负。这是好的,因为损失被定义为结果激活值的负数。
-
正则化技术是通过优化生成合理输入的关键。
-
使用真实数据和多样化的优化数据来理解模型学习到的检测模式。单一的优化数据通常无法代表神经元能够检测到的所有模式。
-
在本教程中,我们使用了最终分类层,这使得找到对选定神经元激活度高的样本变得更容易。如果选择的是一个中间神经元,请确保找到对该神经元激活度最高的数据集。
-
lucent库是基于pytorch的模型,而lucid库则是基于 TensorFlow 的模型,它们专注于图像可视化,但也可以适应其他输入变量类型,如文本。然而,目前在这方面的研究较少,尚未找到针对其他变量类型的良好正则化技术来实现更快的收敛。
总的来说,通过优化技术可视化神经元可以为机器学习(ML)模型的模式和行为提供有价值的洞察,但这需要实验和对输入及正则化技术的谨慎考虑。作为一个额外的收获,通过了解如何执行预测解释和神经网络(NN)解释,我们将发现一种叫做反事实解释的方法,可以使解释在一般情况下更为有效。
发现反事实解释策略
反事实解释或推理是一种通过考虑替代的和反事实的情境或“如果……会怎样”的情况来理解和解释任何事物的方法。在预测解释的背景下,它涉及识别输入数据的变化,这些变化会导致不同的结果。理想情况下,应该识别出最小的变化。在神经网络解释的背景下,它涉及到可视化目标标签或中间潜在特征的反面。这种方法是有意义的,因为它与人类自然解释事件和评估因果关系的方式密切相关,最终能够帮助我们更好地理解模型的决策过程。
人类倾向于从因果关系的角度思考,我们常常探索替代性可能性,以便理解事件或决策的原因。例如,当试图理解某个决策为何做出时,我们可能会问:“如果我们选择了不同的选项,会发生什么?”或“是什么因素导致了这个结果?”这种推理帮助我们识别出影响决策的关键因素,并且让我们从经验中学习。机器学习模型的反事实解释遵循类似的思维过程。通过展示如果输入实例不同将会导致不同预测的替代输入实例,反事实解释帮助我们理解在模型决策过程中,哪些输入特征最为关键。这种解释方式使得用户能够更直观地理解模型的推理过程,并有助于提高他们对模型预测的信任度。
反事实推理补充了特征重要性和神经元可视化技术。结合这些方法,可以更全面地理解模型是如何做出决策的。反过来,这也有助于用户更好地评估模型的可靠性,并根据模型的预测做出更有根据的决策。
摘要
神经网络解释是一种与解释模型做出的预测不同的模型理解过程。通过手动发现真实图像和优化合成图像,使其在选定神经元上高度激活进行解释,是可以一起应用的技术,以理解神经网络。实际上,当你有目标去揭示特定预测标签或类别模式的外观,洞察影响你数据集中某个特定标签预测的因素,或者所有标签的预测时,神经网络的解释会非常有用,并能详细分析预测背后的原因。
在尝试将此技术应用于你的使用案例时,可能会遇到一些小问题,因此不要害怕在你的目标是解释神经网络时,尝试调整本章中介绍的参数和组件。
在下一章,我们将探索你可以从数据和模型中获得的另一个层面的洞察,即偏见与公平性。
第十三章:探索偏见与公平性
有偏见的机器学习模型会产生并放大对某些群体的不公平或歧视性预测。这些模型可能会产生偏见的预测,导致如社会或经济不平等等负面后果。幸运的是,一些国家有反歧视和平等法律,保护少数群体免受不利待遇。机器学习从业者或任何部署有偏见模型的人可能面临的最糟糕情况是收到法律通知,要求支付重罚款,或收到律师函被起诉并被迫关闭其已部署的模型。以下是一些此类情况的例子:
-
叫车应用 Uber 因其面部验证系统在英国受到两个工会的法律诉讼,该系统对深色皮肤人群显示出种族偏见,表现为更频繁的验证错误。这阻碍了他们作为 Uber 司机的工作(
www.bbc.com/news/technology-58831373)。 -
创作者因 YouTube 对他们的种族及其他少数群体歧视而提起诉讼,因为 YouTube 的算法自动删除了他们的视频,并没有给出合理解释,导致他们无法通过广告收入获利(
www.washingtonpost.com/technology/2020/06/18/black-creators-sue-youtube-alleged-race-discrimination/)。 -
Facebook 因其住房广告涉嫌种族、性别、宗教、家庭状态和残疾歧视,遭到美国住房部门起诉并需支付不到 500 万美元的罚款(
www.npr.org/2019/03/28/707614254/hud-slaps-facebook-with-housing-discrimination-charge)。
因此,必须格外小心,避免对机器学习模型所接触的基础数据和环境中的敏感和受保护属性产生偏见。在本章中,我们将逐步探讨这个话题,从探索偏见的类型开始,学习检测和评估识别偏见与公平性的方法,最后探讨减轻偏见的途径。本章将介绍的概念和技术适用于所有机器学习模型。然而,偏见缓解是一个例外;我们将在其中探讨一种特定于神经网络的方法,该方法能够可靠地缓解偏见。更正式地说,我们将涵盖以下主题:
-
探索偏见的类型
-
理解 AI 偏见的来源
-
发现偏见与公平性评估方法
-
评估深度学习模型的偏见与公平性
-
针对不同使用案例量身定制偏见与公平性措施
-
减少 AI 偏见
技术要求
本章包含一些 Python 编程语言中的实际实现。要完成这些内容,您需要在计算机上安装以下库:
-
pandas -
matplotlib -
scikit-learn -
numpy -
pytorch -
transformers==4.28.0 -
accelerate==0.6.0 -
captum -
catalyst
代码文件可以在 GitHub 上获取,链接为github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_13。
探索偏差的类型
偏差可以被描述为对某个特定观点、意见或信仰系统的自然倾向或倾向,无论其是否被视为正面、中立或负面。另一方面,AI 偏差特指当数学模型延续其创建者或基础数据中固有的偏差时发生的情况。需要注意的是,并非所有信息都被视为偏差,有些信息也可以被视为知识。偏差是一种主观信息,而知识指的是通过学习、经验或研究获得的事实性信息、理解或意识。换句话说,知识是不带偏见的真理。
注意
不要将本书中的偏差与机器学习中臭名昭著的“偏差与方差”概念中的偏差混淆。在这个概念中,偏差指的是机器学习模型在执行某个任务时的简单性。为了完整性,方差指定了模型对数据特征变化的敏感性。在这里,高偏差对应于低方差和欠拟合行为,表明机器学习模型过于简单。低偏差则对应于高方差和过拟合行为。
在本书中,我们将以更口语化的方式使用偏差一词。诸如种族、性别和年龄等更为人知的偏差特征被视为社会偏差或刻板印象。然而,偏差的范围要广得多。以下是一些其他类型偏差的有趣例子:
-
文化偏差:文化视角、价值观和规范对判断、决策和行为的影响。它可以通过偏倚的数据收集、失衡的训练数据或反映文化偏见的算法在机器学习模型中表现出来。
-
认知偏差:在思维或决策过程中系统性地偏离理性思考的模式。让我们看几个认知偏差的例子,并探讨这些偏差在机器学习项目中可能表现出的现象:
-
确认偏差:偏向于支持现有信念的数据。
-
锚定偏差:在模型训练过程中过度强调某些特征或变量。
-
可用性偏差:依赖于易于获取的数据源,可能忽视相关但不易获取的数据源。
-
过度自信偏差:高估模型的能力或准确性。
-
事后偏差:在观察到模型预测结果后,认为这些预测结果是可以预见的。
-
自动化偏差:过度依赖模型输出而不进行批判性评估
-
框架偏差:通过有偏的数据呈现影响模型的学习过程
-
选择偏差:非随机抽样导致的不具代表性数据
-
抽样偏差:由于样本不具代表性导致的数据偏倚
-
报告偏差:由于个人的偏好或信仰导致的错误陈述
-
-
算法偏差:算法中存在的任何偏差,如机器学习算法。另一个偏差的例子是聚合偏差,它是由于数据分组或聚合方法导致的预测偏倚。
-
测量偏差:数据收集和测量中的不准确或错误。
这些偏差类别仅仅是冰山一角,可能延伸到非常特定的偏差类别,例如政治偏差、行业偏差、媒体偏差等。现在我们了解了偏差的概念及其涵盖的范围,接下来让我们一起探讨 AI 偏差的来源。
理解 AI 偏差的来源
AI 偏差可能发生在深度学习生命周期的任何阶段。让我们逐个了解这些阶段中的偏差:
-
规划:在机器学习生命周期的规划阶段,随着项目目标、数据收集方法和模型设计的决定,偏差可能会出现。偏差可能源于主观选择、假设或使用不具代表性的数据源。项目规划者需要保持批判性的视角,积极考虑潜在的偏差,吸纳多元观点,并优先考虑公平性和伦理问题。
-
数据准备:这一阶段包括以下几个阶段:
-
数据收集:在数据收集阶段,如果收集的数据未能准确代表目标群体,偏差可能会悄然出现。多个因素可能导致这种偏差,包括抽样偏差、选择偏差,或者某些群体的代表性不足。这些问题可能导致创建不平衡的数据集,无法反映目标群体的真实多样性。
-
数据标注:偏差也可能渗透到数据标注过程中。每个标注员可能有自己固有的偏见,无论是有意识还是无意识,这些偏见可能会影响他们在给数据标注时的决策。如果标注员缺乏多样性或全面的培训,他们的偏见可能会渗入到标注中,最终导致开发出带有偏见的模型,进而延续不公平和歧视。因此,最终的综合数据可能包含多种偏见,甚至可能彼此冲突,从而造成学习过程中的困难。
-
-
模型开发:偏差可以通过两种方式在深度学习模型中引入:
-
特征选择:偏差可能来源于选择用于模型训练的特征。如果某些特征与受保护属性(如种族或性别)相关,模型可能会无意中学习并强化这些偏见,导致歧视性结果。
-
预训练模型:一个预训练的深度学习模型可能是一个有偏的模型。例如,如果模型在有偏的数据上进行训练,它可能会在预测中学习并延续这些偏见。即使进行了微调,偏见也不太可能消失。
-
-
传递模型洞察:偏见特别容易出现在解释模型行为时。理解和解释模型内部工作原理的过程涉及主观推理,并且容易受到偏见的影响。模型洞察的解释在很大程度上依赖于相关人员的视角和先入为主的观念,这可能会根据他们自己的信仰、经验或隐性偏见引入偏见。必须意识到这些潜在的偏见,并力求客观公正,以避免误解或加剧现有偏见。批判性思维和多元视角对确保传递的洞察准确反映模型行为、避免引入额外偏见至关重要。
-
模型部署:此阶段涵盖了模型部署时可能出现的偏见,包括以下几个组件:
-
用户互动:在模型部署过程中,当用户提供反馈或响应时,可能会引入偏见,特别是在反馈本身存在偏见,或系统根据用户特征做出不同响应时。例如,ChatGPT 界面中的聊天历史机制允许用户提供有偏的输入。
-
人类参与偏见:当人工审阅者或操作员根据模型预测做出决策时,可能会引入他们自己的偏见或不公正地解读输出结果。这会影响决策过程的公正性。
-
环境偏差:某些特征在未见过的领域可能会被不同地处理和感知,从而导致数据漂移。模型在开发阶段被评估为无偏,但随着新数据的加入,它仍然可能产生有偏的预测。
-
重新训练的偏见数据源:新数据可以被收集并标注用于重新训练,这可能成为偏见的来源。
-
-
模型治理:偏见可能出现在负责监控已部署模型的人员需要为各种类型的漂移设定阈值(将在第十六章中介绍,治理深度学习模型)、分析预测摘要和检查数据摘要时。设定这些阈值可能会基于主观决策或假设引入偏见。此外,在分析预测和数据摘要时,如果没有以谨慎和批判的思维方式进行,可能会忽视某些偏见,或不小心强化现有偏见。必须保持对这些偏见的敏感性,确保监控和分析过程严格进行,注重公正性和准确性。
现在我们已经发现了深度学习生命周期中各阶段可能的偏差来源,我们准备深入探索偏差检测和公平评估方法。
发现偏差和公平评估方法
公平和偏差是对立的概念。公平旨在确保所有个体或群体在决策中得到公平和平等的对待,而偏差则指不公平或不平等的对待。减少偏差是实现公平的关键步骤。偏差可能以不同形式存在,解决所有潜在偏差是复杂的。此外,理解在某个方面实现公平并不意味着完全消除一般性的偏差也很重要。
要了解我们的数据和模型在多大程度上存在偏差以及多么公平,我们需要一组偏差和公平度量来客观地衡量和评估。这将启用一个反馈机制,迭代且客观地减少偏差并实现公平。接下来,让我们来看一些你必须掌握的强健的偏差和公平度量工具,以实现公平:
-
基于平等代表性的度量:这一组度量关注的是数据或决策结果的平等比例,而不考虑错误:
- 不同影响:不同影响考察的是模型是否公平地对待不同群体,或者它们在所获得的结果中是否存在显著的相对差异,通过计算群体之间有利结果比例的比值来进行。选定群体在选定属性下的不同影响可以使用以下公式计算:
不同影响(组 A) =
(组 A 的正预测比例 ___________________________________________________________ 参考组或其他群体的正预测比例)
跨群体的不同影响可以求平均,以获得单一的全局代表值。
- 统计平等差异:这一方法与不同影响具有相似的益处,但通过使用差值而非比值,提供了绝对差异度量。绝对差异在你可以且需要将值转化为实际影响时很有用,比如基于新样本大小的被歧视个体数量。它可以使用以下公式计算:
统计平等差异 = |(特权组的正预测比例) − (非特权组的正预测比例)|
-
基于平等错误的度量:这些度量考虑的是不同群体之间错误率的偏差。
- 平均赔率差异 (AOD):这衡量了群体之间真实正例和假正例结果的平均差异。AOD 是通过计算不同群体间赔率差异的平均值来得出的。某个特定群体的赔率差异是通过以下公式计算的:该群体的正预测赔率与参考群体的正预测赔率之间的差异:
AOD = (1 / n) * Σ[(TPR _ ref − TPR _ i) + (FPR _ ref − FPR _ i)]
这里,n 是群体的总数,TPR _ i 是群体 i 的真实正例率(灵敏度),FPR _ i 是群体 i 的假正例率(误报率),TPR _ ref 是参考群体的真实正例率,FPR _ ref 是参考群体的假正例率。
- 平均绝对赔率差异 (AAOD):这与 AOD 类似,但在各个群体的计算中增加了绝对项,计算方式如下:
AOD = (1 / n) * Σ[|(TPR _ ref − TPR _ i) + (FPR _ ref − FPR _ i)|]
当你关心一般性差异而不仅仅是群体差异是否被偏好时,应该使用这个方法,而不是 AOD。
- 通过广义熵指数 (GEI) 衡量分配公平性:这是通过仅使用数值结果,设计来衡量基于整个群体中个体分布的贫富差距。GEI 的公式如下:
GE(α) = 1 / na(a − 1) ∑ i=1 n (na w a− n),a ≠ 0,1,
GE(α) = log(n) + ∑ i=1 n w i log(w i),a = 1,
GE(α) = − log(n) − 1 / n ∑ i=1 n log(w i),a = 0
其中,E T = ∑ i=1 n E i,w i = E i _ E T
E 是特定实体的选定属性值,E T 是所有实体的总和值,n 是实体或个体的总数。通过 GEI 的 α 参数可以配置两种基本的不平等概念:
-
泰尔指数:衡量所有群体之间所有个体的总体不平等。其 α 值为 1。
-
变异系数:通过计算群体个体的变异性来衡量不平等。群体的差异越大,该群体的不平等也越大。其 α 值为 2。
如果你想了解整体的不平等情况,可以使用泰尔指数作为主要选择,而当你想按群体理解不平等时,可以切换到变异系数。
-
个体公平性度量:基于相似个体的结果差异或相似性是一个单一的基于个体的度量。像 KNN 这样的邻近算法可以让你考虑个体的多个关联特征,并基于相似的例子计算公平性度量。你需要执行以下步骤:
-
使用 KNN 方法为每个个体找到具有相似特征的若干个例子,排除受保护属性。
-
使用选择的公平性度量计算结果的不平等性。这里最常用的度量是相似示例与个体结果的平均相似度。
-
-
通过平衡准确度的公平性准确度性能度量:这为分类模型的性能提供了平衡评估,特别是在处理不平衡数据集时。平衡准确度是通过计算每个类别的准确度的平均值来得到的。
虽然我们在这里介绍的度量指标仅涵盖了该领域中部分可用的偏差和公平性度量,但它足够通用,可以满足大多数机器学习的应用场景。现在,让我们探讨如何在深度学习项目中实际使用这些度量指标来衡量偏差和公平性。
评估深度学习模型的偏差和公平性
在这个实际示例中,我们将探讨广为人知的面部识别的现实应用场景。这个实际示例将用于接下来章节中偏差缓解的实际实施。面部识别的基础是生成特征向量,这些特征向量可以用来执行基于 KNN 的分类,这样新面孔就不需要经过额外的网络训练。在这个示例中,我们将训练一个分类模型,并使用传统的基于分类准确度的度量进行评估;我们不会演示该应用场景的识别部分,后者可以让我们处理未知的面部身份类别。
这里的目标是确保结果的面部分类模型具有较低的性别偏差。我们将使用一个公开可用的面部数据集,名为BUPT-CBFace-50,它涵盖了各种不同的面部表情、姿势、光照条件和遮挡的面部图像。该数据集由 10,000 个面部身份类别的 500,000 张图像组成。在这个实际示例中,您将需要至少 12 GB 内存的 GPU,以便在合理的时间内完成训练。在开始示例之前,请从官方来源下载数据集(buptzyb.github.io/CBFace/?reload=true)。您可以在与项目相同的目录中找到它。
让我们从一步一步的代码演示开始,再次使用 Python 和 pytorch 库,以及 catalyst:
-
首先,让我们导入必要的库,包括作为深度学习框架的
pytorch、用于跟踪和比较的mlflow、用于预训练 ResNet50 模型的torchvision、用于高效处理 PyTorch 模型的catalyst以及用于简单图像数据处理的albumentations:import os import albumentations as albu import numpy as np import pandas as pd import torch import torch.nn as nn from albumentations.pytorch.transforms import ToTensorV2 from PIL import Image from sklearn.model_selection import StratifiedShuffleSplit from torch.optim import SGD, Adam from torch.utils.data import DataLoader, Dataset from torchvision import models from torchvision.models import resnet50 import mlflow from catalyst import dl, utils, metrics from catalyst.contrib.layers import ArcFace from catalyst.loggers.mlflow import MLflowLogger from captum.attr import IntegratedGradients from captum.attr import NoiseTunnel from captum.attr import visualization as viz os.environ["CUDA_VISIBLE_DEVICES"] = "0"最后一行将设置您的机器的第一个 GPU 为 CUDA 可见,CUDA 是 GPU 的计算接口,用于执行计算。
-
接下来,我们将定义用于不同组件的配置。这将包括训练过程的特定参数,如批次大小、学习率、训练轮数、在训练过程中停止前的早期停止轮数,以及等待验证指标改进的轮数后才会减少学习率:
batch_size = 64 val_batch_size = 100 lr = 0.05 num_epochs = 20 early_stopping_epochs = 12 reduce_lr_patience = 3 -
此外,它还将包括我们为计算基于性别的偏差和公平性指标所选择的特权和非特权群体的规定。数据集中男性数量大约是女性的两倍,因此这里的特权群体预计为
Male:priviledged_group = 'Male' unpriviledged_group = 'Female' -
最后,我们将为
mlflow实验名称、模型保存目录和一些暂时不启用的额外参数设置名称:experiment_mlflow = "bias_mitigation_v1" logdir = 'experiments/face_modelv1' only_male=False distill_model=False -
接下来,我们将加载包含数据集元数据的 CSV 文件,主要包括已下载并解压的
BUPT_CBFace数据集的图像路径:train = pd.read_csv('face_age_gender.csv') image_path = train['image_path'].values targets = train['target'].values gender_data = train['gender'].values -
此外,我们还将设置
name2class映射器,以及类 ID 目标数组和类别数量:name2class = {name: idx for idx, name in enumerate(sorted(set(targets)))} id_targets = np.array([name2class[target] for target in targets]) num_classes = len(name2class) -
接下来,我们将对数据进行分层切分,将其划分为训练集和验证集,确保两个数据集都包含所有可用的人脸身份类别:
splitter = StratifiedShuffleSplit(test_size=.20, n_splits=2, random_state = 7) split = splitter.split(image_path, targets) train_inds, val_inds = next(split) train_images = image_path[train_inds] val_images = image_path[val_inds] train_targets = id_targets[train_inds] val_targets = id_targets[val_inds] train_gender_data = gender_data[train_inds] val_gender_data = gender_data[val_inds] if only_male: pass最后的
if语句逻辑将在下一节关于偏差缓解的内容中讲解。对于后续步骤,将pass的使用视为一个指示符。 -
接下来,我们将定义基于 ResNet50 模型的模型。我们将在这里使用 ARCFace 层,并以 ResNet50 为基础,这是一种度量学习算法。它利用角度边距损失来增强学习到的人脸嵌入的区分能力,从而实现更精确、更鲁棒的人脸识别,能够应对不同的姿势、光照和身份变化:
class ARCResNet50(nn.Module): def __init__(self, num_classes): super(ARCResNet50, self).__init__() self.model = models.resnet50(pretrained=True) s=2**0.5*np.log(num_classes - 1) s=13 self.model.fc = nn.Linear(self.model.fc.in_features, self.model.fc.in_features) self.head = ArcFace(self.model.fc.out_features, num_classes, s=s, m=0.15) def get_last_conv_features(self, x): pass def special_forward(self, x, targets=None): pass def forward(self, x, targets=None): outputs = self.model(x) outputs = self.head(outputs, targets) return outputs -
接下来,我们将初始化模型,分配给 GPU 使用,定义交叉熵损失变量,定义 SGD 优化器变量,并定义在验证性能下降时减少学习率的引擎:
if distill_model: pass model = ARCResNet50(num_classes=num_classes) model.to(device) criterion = nn.CrossEntropyLoss() optimizer = SGD(model.parameters(), lr=lr, momentum=0.9, weight_decay=1e-5) scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=reduce_lr_patience, factor=0.1) -
接下来,我们需要定义 PyTorch 数据集类,以接收图像文件路径和敏感属性,这些敏感属性包括性别数据、目标和指定的 albumentation 变换。最后一个变量将在下一节中使用:
class ImagesDataset(Dataset): def __init__(self, files, sensitive_attributes, targets=None, transforms=None, teacher_model_features=None): self.files = files self.sensitive_attributes = sensitive_attributes self.targets = targets self.transforms = transforms self.teacher_model_features = teacher_model_features def __len__(self): return len(self.files) def __getitem__(self, index): file = self.files[index] img = np.array(Image.open(file)) if self.transforms is not None: img = self.transforms(image=img)["image"] if self.targets is None: return img target = self.targets[index] sensitive_attribute = self.sensitive_attributes[index] if self.teacher_model_features is None: return img, sensitive_attribute, target else: teacher_model_feature = torch.from_numpy(self.teacher_model_features[index]) return img, sensitive_attribute, target, teacher_model_feature -
现在,我们要应用来自
albumentation库的一组简单变换操作。我们将定义一个方法,返回一个用于训练的带增广的变换实例,以及用于验证的无增广的变换实例。两者都需要变换实例将图像值转换为 PyTorch 张量:def get_transforms(dataset: str): if dataset.lower() == "train": return albu.Compose([ albu.Resize(224, 224), albu.HorizontalFlip(), albu.Normalize(), ToTensorV2() ]) else: return albu.Compose([ albu.Resize(224, 224), albu.Normalize(), ToTensorV2()]) -
接下来,让我们初始化数据集并随后加载数据集:
if distill_model: pass else: train_dataset = ImagesDataset(train_images, train_gender_data, train_targets, get_transforms('train')) val_dataset = ImagesDataset(val_images, val_gender_data, val_targets, get_transforms('valid')) loaders = {"train": DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=8), "valid": DataLoader(val_dataset, batch_size=val_batch_size, shuffle=False, num_workers=8)} -
现在,我们将定义辅助方法来帮助计算多类偏差和公平性指标,这些方法包括执行安全除法处理和零除法,防止 NaN 值,并计算假正例、真负例、总正例、总负例和总数据。由于这是一个多类问题,我们必须根据类别选择宏平均或微平均统计量。微平均方法将所有样本平等对待,而宏平均方法将所有类别平等对待。宏平均方法有一个潜在问题:如果某个少数类别的表现很好,它会给人一种模型总体表现很好的虚假印象。因此,我们将在这里使用微平均:
def compute_classwise_stats(y_pred, y_true): unique_classes = np.unique(y_true) num_classes = len(unique_classes) false_positives = np.zeros(num_classes) total_negatives = np.zeros(num_classes) true_positives = np.zeros(num_classes) total_positives = np.zeros(num_classes) for c_idx in range(num_classes): class_label = unique_classes[c_idx] class_predictions = (y_pred == class_label) class_labels = (y_true == class_label) false_positives[c_idx] = np.sum(class_predictions & ~class_labels) total_negatives[c_idx] = np.sum(~class_labels) true_positives[c_idx] = np.sum(class_predictions & class_labels) total_positives[c_idx] = np.sum(class_labels) return { "false_positives":false_positives, "total_negatives": total_negatives, "true_positives": true_positives, "total_positives": total_positives, "total": total_negatives + total_positives, } def safe_division(a, b): return a/b if b else 0.0 -
最后,我们将利用这些辅助方法定义一个方法,使用常见的计算结果来计算四个偏差和公平性指标——即,差异影响、统计平等差异、AOD 和 AAOD:
def compute_multiclass_fairness_metrics( y_pred, y_true, sensitive_attribute, priviledged_group, unpriviledged_group ): -
我们将首先使用辅助方法获得特权组和非特权组的假正例、真负例、总正例、总负例和总数据:
y_pred = y_pred.argmax(1) group_stats = {} for group in [priviledged_group, unpriviledged_group]: group_idx = sensitive_attribute == group group_stats[group] = compute_classwise_stats( y_pred[group_idx], y_true[group_idx]) -
接下来,我们将使用计算出的组统计数据计算特权组和非特权组的真实正例比例,以便直接用于计算差异影响和统计平等差异:
disparities = [] priviledged_true_positive_ratio = safe_division( np.sum(group_stats[priviledged_group]["true_positives"]),np.sum(group_stats[unpriviledged_group]["total"])) unpriviledged_true_positive_ratio = safe_division( np.sum(group_stats[unpriviledged_group]["true_positives"]),np.sum(group_stats[unpriviledged_group]["total"])) -
现在,我们将使用真实正例比例计算前面提到的两个指标:
disparate_impact = safe_division(unpriviledged_true_positive_ratio, priviledged_true_positive_ratio) statistical_parity_diff = priviledged_true_positive_ratio - unpriviledged_true_positive_ratio -
最后,我们将使用真实正例率
tpr和假正例率fpr来计算 AOD 和 AAOD:for group in [priviledged_group, unpriviledged_group]: group_stats[group]["fpr"] = safe_division( np.sum(group_stats[priviledged_group]["false_positives"]),np.sum(group_stats[priviledged_group]["total_negatives"])) group_stats[group]["tpr"] = safe_division( np.sum(group_stats[priviledged_group]["true_positives"]), np.sum(group_stats[priviledged_group]["total_positives"]) ) AOD = ( (group_stats[unpriviledged_group]["fpr"] - group_stats[priviledged_group]["fpr"]) + (group_stats[unpriviledged_group]["tpr"] - group_stats[priviledged_group]["fpr"]) ) / 2 AAOD = ( np.abs(group_stats[unpriviledged_group]["fpr"] - group_stats[priviledged_group]["fpr"]) + np.abs(group_stats[unpriviledged_group]["tpr"] - group_stats[priviledged_group]["fpr"]) ) / 2 return { "disparate_impact": disparate_impact, "statistical_parity_diff": statistical_parity_diff, "average_odds_diff": AOD, "average_abs_odds_diff": AAOD } -
我们将在训练过程中计算这四个指标,并能够在训练时监控和追踪这些指标。为了追踪实验,我们将记录参数并监控它们在每次迭代和每个 epoch 的表现。我们将使用 MLflow 来完成这个操作。让我们定义
mlflow日志记录器并记录我们在步骤 2中定义的参数:mlflow_params = dict( batch_size=batch_size, lr=lr, num_epochs=num_epochs, early_stopping_epochs=early_stopping_epochs, reduce_lr_patience=reduce_lr_patience, experiment_mlflow=experiment_mlflow, logdir=logdir, only_male=only_male, distill_model=distill_model, ) all_metrics = [ "loss", "accuracy", "disparate_impact","statistical_parity_diff", "average_odds_diff","average_abs_odds_diff" ] mlflow_logger = MLflowLogger(experiment=experiment_mlflow, tracking_uri="experiment/") mlflow_logger.log_hparams(mlflow_params) -
由于我们稍后需要一个稍微专业化的流程来计算自定义指标并执行偏差缓解方法,我们将使用
catalyst定义一个自定义的 runner,用于训练 ResNet50 模型。我们需要定义四个方法的三条自定义逻辑:on_loader_start(初始化指标聚合器功能)、handle_batch(获取损失)、on_loader_end(最终汇总批次指标并更新学习率调度器)以及get_loggers(将数据记录到 MLflow)。让我们从定义on_loader_start开始:class CustomRunner(dl.Runner): def on_loader_start(self, runner): super().on_loader_start(runner) self.meters = {key: metrics.AdditiveMetric(compute_on_call=False) for key in all_metrics} -
接下来,我们将定义批次处理逻辑,它将以自定义方式从批次数据加载器加载数据,使用在 runner 中初始化的模型执行前向传播,然后计算损失、准确率和多类公平性指标:
def handle_batch(self, batch): is_distill_mode = len(batch) == 4 if is_distill_mode: pass else: features, sensitive_attribute, targets = batch logits = self.model(features, targets) loss = self.criterion(logits, targets) accuracy = (logits.argmax(1) == targets).float().mean().detach().cpu() batch_metrics = { "loss": loss.item(),"accuracy": accuracy.item()} batch_metrics.update( **compute_multiclass_fairness_metrics(logits.detach().cpu().numpy(), targets.detach().cpu().numpy(), np.array(sensitive_attribute),priviledged_group, unpriviledged_group)) -
在同一方法中,我们还需要更新当前批次指标和聚合批次指标,以便它们能够正确地记录,并在训练模式下执行反向传播,而不是验证模式:
self.batch_metrics.update(batch_metrics) for key in all_metrics: self.meters[key].update( self.batch_metrics[key], self.batch_size ) if self.is_train_loader: loss.backward() self.optimizer.step() self.optimizer.zero_grad() -
现在,我们必须定义最后两个简单的方法:
def on_loader_end(self, runner): for key in all_metrics: self.loader_metrics[key] = self.meters[key].compute()[0] if runner.is_valid_loader: runner.scheduler.step(self.loader_metrics[self._valid_metric]) super().on_loader_end(runner) def get_loggers(self): return { "console": dl.ConsoleLogger(), "mlflow": mlflow_logger } -
最后,我们将初始化运行器并训练模型:
runner = CustomRunner() runner.train( model=model, criterion=criterion, optimizer=optimizer, loaders=loaders, logdir=logdir, num_epochs=num_epochs, valid_loader="valid", valid_metric="loss", # loss" minimize_valid_metric=True, fp16=False, verbose=True, load_best_on_end=True, scheduler=scheduler, callbacks=[ dl.EarlyStoppingCallback( patience=early_stopping_epochs, loader_key="valid", metric_key="loss", minimize=True ), dl.CheckpointCallback( logdir=logdir, save_best=True, load_best_on_end=True, metric_key='loss' ) ] )图 13.1 显示了
mlflow绘制的训练和验证集每个轮次的准确度、AOD、差异影响和统计平等差异的性能图:

图 13.1 – ResNet50 模型按训练轮次的性能图
验证集得分为 AAOD 和 AOD 各 0.424,准确度 0.841,差异影响 0.398,统计平等 0.051。
-
图表显示,尽管准确度随着每一轮训练不断提高,但偏差和公平性度量逐渐变差,但停滞在某个值。最低偏差的模型出现在第 0 轮训练时,那时一切几乎为零,包括准确度。尽管该模型没有偏差,但它根本没有任何用处。另一个有趣的观察是在第 16 轮训练时,模型成功地在男性样本上提高了验证准确度,而 AOD 值在同一时刻变得更高。根据具体情况,你可以选择第 15 轮训练的模型,该模型的准确度得分较好,但不是最佳的,且偏差得分较低。
-
为了进行更深的分析,让我们看看模型在使用 Captum 的集成梯度技术进行预测时关注的重点。我们将可视化大多数面向正面的图像。为了做到这一点,我们必须定义必要的转换方法:
resize_transform = albu.Resize(224, 224) norm_transform = albu.Compose([ albu.Normalize(), ToTensorV2() ]) -
现在,让我们定义要可视化的正面人脸:
val_df = pd.read_csv('val_pose_info.csv') straight_indexes = val_df[ (val_df['pitch']>-10) & (val_df['pitch']<10) & (val_df['yaw']>-10) & (val_df['yaw']<10) ].index.values -
最后,让我们可视化关注区域:
integrated_gradients = IntegratedGradients(model) noise_tunnel = NoiseTunnel(integrated_gradients) for val_idx in straight_indexes[:5]: #range(1): image_path = val_images[val_idx] pred_label_idx = val_targets[val_idx] pil_image = Image.open(image_path) img = np.array(pil_image) transformed_image = resize_transform(image=img)["image"] input = norm_transform(image=img)["image"].unsqueeze(0) attributions_ig_nt = noise_tunnel.attribute( input, nt_samples=5, nt_type="smoothgrad_sq", target=int(pred_label_idx) ) _ = viz.visualize_image_attr_multiple( np.transpose(attributions_ig_nt.squeeze().cpu().detach().numpy(), (1,2,0)), transformed_image, ["original_image", "heat_map"], ["all", "positive"], cmap="turbo", show_colorbar=True )图 13.2 显示了原始图像和模型的关注区域:

图 13.2 – 训练的 ResNet50 模型的显著性解释结果
- 这些可视化结果表明,模型在处理女性面孔时存在偏差,错误地将注意力集中在头发上。对于男性,模型并没有关注头发。模型也稍微关注了白色背景。我们将在下一节学习如何去除这种偏差。
现在我们理解了一些常见的偏差和公平性度量标准,我们需要知道在不同的使用案例中应使用哪些度量标准。
在不同使用案例中定制偏差和公平性度量
确定偏差和公平性度量标准以适应我们使用案例的过程,和确定一般模型性能评估度量标准的过程类似,正如在 第十章《探索模型评估方法》的工程化基础模型评估度量标准部分中所介绍的那样。所以,一定要去查看那个话题!然而,偏差和公平性有独特的方面,需要额外的启发式推荐。之前,我们探索了属于同一度量组的度量推荐。现在,让我们探索四个度量组的通用推荐:
-
当存在敏感和受保护属性时,平等代表性始终是期望的。因此,当你看到这些属性时,务必在数据和模型中使用基于平等代表性的度量。示例包括种族、性别、宗教、性取向、残疾、年龄、社会经济地位、政治倾向和犯罪历史。
-
预测性能一致性是处理敏感和受保护属性的机器学习模型的另一个期望特性。因此,当你看到这些属性时,务必在模型中使用基于错误的平等度量。
-
分布公平性度量和平等代表性度量都衡量不平等。然而,分布公平性直接作用于连续变量,而平等代表性度量作用于分类变量。可以对连续变量进行分箱处理,将其转化为类别,但如何选择合适的分箱策略并不简单。因此,当衡量偏差和公平性的变量是连续变量时,应使用分布公平性度量。
-
考虑所有潜在的偏差和公平性方面,并使用选择的偏差和公平性度量分别进行衡量。
- 假设有一个机器学习模型用于预测贷款审批。公平性的一个方面是确保贷款审批在不同的群体之间(如种族或性别)是公平的。确保数据的平等代表性以及由此产生的模型可以帮助你实现这一目标。然而,仅仅关注平等的代表性可能无法捕捉到完整的情况。例如,即使不同群体之间的整体贷款批准率是平等的,不同群体的利率差异可能非常显著。这种利率的差异可能导致不公平和不平等的结果,因为某些群体可能会被收取更高的利率,从而导致财务上的不利影响。使用分布公平性度量进行额外评估和监控可以帮助你理解影响,并帮助针对不利群体的偏差缓解。
-
在部署机器学习模型并在模型推断过程中接收个体数据时,评估结果的个体公平性非常有用。这里可以设置一个阈值,当个体公平性得分过高时,触发警报,并需要人工审查员进行评估并做出手动决策。
-
分别计算这些度量并通过敏感群体进行可视化对比,以感受群体间的公平性。这可以帮助你制定针对易受影响群体的偏差缓解响应。
-
预测解释可能有助于理解偏差和公平性的原因。
-
偏差和公平性度量可能与基于准确性的性能度量发生冲突。
此外,有两个全球性对立的观点值得一提,它们会影响你如何看待公平性:
-
我们都平等 (WAE):这一概念认为,由于数据中固有偏见的存在,数据可能无法准确地代表现实。
-
所见即所得 (WYSIWYG):这一观点认为,数据所呈现的信息反映了一个不带偏见的现实表现,即便它揭示了不平等现象。
任何一方极端的观点都将导致无法构建模型(WAE)或对公平偏见缺乏足够的重视,最终导致负面后果,正如本章前面提到的(WYSIWYG)。为了在我们的应用场景中创建一个成功的机器学习模型,我们需要战略性地平衡这两种观点,以便同时满足准确性表现和公平性要求。这里采用的一个好策略是运用常识,接受因果关系中看似合逻辑的部分(WYSIWYG),并采取公平的流程帮助我们理解和缓解偏见的局部方面(WAE),即使不能实现真正的公平。
以下是一些值得采用的、最有意义的观点示例:
-
采用 WYSIWYG:在个性化广告领域,使用机器学习的 WYSIWYG 观点将包括根据观察到的用户行为和偏好定制广告。例如,如果一个用户频繁与健身相关的内容互动,系统将展示健身设备或健身会员卡的广告。目标是提供与用户兴趣和需求相符的个性化体验。在这个应用场景中,任何关于 WAE 观点的内容都会使项目失败,因为它与个性化的目标相冲突。
-
采用类似于 75% WYSIWYG 和 25% WAE 的策略:在使用机器学习做出招聘决策的情境中,WAE 观点有助于倡导对所有候选人的平等考虑,避免性别、种族或其他受保护属性相关的偏见。WYSIWYG 观点则会允许仅根据候选人的资历和技能来构建机器学习模型。创建一个公平且无偏的选拔过程的策略将有助于为所有应聘者提供平等机会,同时仍能成功地构建一个有效的机器学习模型。
在度量标准方面,基于平等代表性的度量标准组与 WAE 观点相关,而基于平等错误的度量标准组与 WYSIWYG 观点相关。因此,如果在应用场景中涉及这两种观点,请选择这两组度量标准。
接下来,我们将探讨如何减轻机器学习模型中的偏见。
减轻 AI 偏见
AI 偏见是一种算法偏见,可能来自模型本身的学习过程,或者来自用于学习的数据。缓解偏见的最明显解决方案不是程序化的缓解方法,而是确保在收集数据时采用公平的流程。数据收集和准备过程只有在确保结果数据不仅通过敏感属性平衡,而且确保不包含所有固有和系统性偏见时,才能算是完全公平的。
不幸的是,基于敏感属性的平衡数据集并不能保证模型的公平性。不同子组之间,或数据中与多种因素相关的联动组,可能存在外观上的差异,这可能导致系统偏见。然而,当数据集相较于未考虑可观察敏感群体时是平衡的,偏见可以在一定程度上得到缓解。但这些属性到底是什么呢?在具有明确定义列名的表格结构数据中,识别数据属性可能更容易,但对于深度学习用的非结构化数据,几乎不可能涵盖所有可能的属性。
为了更深入地探讨实际示例,文本数据可以包含大量属性及其示例,例如语言、类型、主题、长度、风格、语气、时间段、作者身份、地理来源和文化视角。图像数据也可以包含大量属性及其示例,例如主题/内容、视角、光照、构图、颜色、纹理、分辨率、方向、背景和文化相关性。最后,音频数据也可以包含大量属性,例如类型、语言、时长、音质、乐器、声乐风格、节奏、情绪、文化影响和录音环境。
确保在所有方面的平等代表性很困难——尤其是当数据轻松获得且原始数据本身已经高度不平衡,仅在某些类别中有少量示例,而其他类别有大量示例时。理想情况下,偏见缓解应始终从数据准备阶段开始执行。然而,如果无法做到这一点,程序化的偏见缓解方法可以在数据收集后应用。
为了更清楚地了解这些方法,请查看图 13.3,它展示了各种减少偏见的 approaches:

图 13.3 – 在两个机器学习生命周期阶段下,偏见缓解的四个步骤
程序化的偏见缓解提供了可以在模型构建过程的三个不同阶段应用的方法:
-
预处理:这是数据准备阶段中的一部分。以下是属于此组的一些偏见缓解方法示例:
-
消除受保护属性在模型中的使用。然而,关于受保护属性的信息可能仍然会通过其他相关属性呈现。此外,某些属性与预测所需标签的其他关键信息紧密相连,且无法轻易移除。例如,面部图像中的性别。
-
不平衡影响移除器,可在 AIF360 开源库中使用。它通过删除数据行来平衡数据集群体的比例,从而实现更好的不平衡得分。
-
在训练过程中,对特权类别损失加权,使其低于弱势类别的损失,从而使各类别之间的误差更加平等。
-
针对弱势群体的数据增强。增强技术可以通过两种方式增加数据变化:
-
通过在弱势群体中添加更多数据变化进行增强,有助于提高该群体的准确性,并有助于平衡错误率,特别是当弱势群体的样本不足时。
-
反事实角色反转增强技术将特权群体反转为弱势群体,反之亦然,允许实现平等代表性。以下是根据变量类型可以使用的一些增强技术:
-
• 文本:使用词语交换
• 所有变量类型:使用风格迁移技术。以下是一些示例技术:
-
使用训练过的 StarGAN 生成式 AI 技术,这是一种图像生成器,能够反转一个人的性别、改变一个人的年龄等等。
-
通过减少选定中间层与图像之间的距离,优化期望图像以模仿另一图像的风格。这种方法被称为输入优化传递,基于上一章提到的神经解释技术。
-
针对已部署模型,通过替换输入进行针对性的反事实角色反转,而不是额外的增强。这允许您在部署过程中显式地控制结果的平等性。
-
OpenAI 在 Dall-E 的文本到图像提示中随机将“male”更改为“female”,因为该模型有偏向性,倾向于将男性描绘为教授和 CEO 等角色。
-
-
处理过程中:这是模型开发机器学习生命周期阶段的一部分,是训练模型的过程。以下是属于这一组的偏差缓解方法的示例:
-
知识蒸馏:这一概念在第八章《探索监督深度学习》中介绍。其思路是,使用更大且更具代表性数据集训练的教师模型,相比于用较小定制数据集训练的学生模型,更不易产生偏见。理想情况下,教师模型中的公平性将被蒸馏到学生模型中。然而,当用有偏的教师模型进行蒸馏时,知识蒸馏也可能会导致生成的学生模型产生更强的偏见。
-
对抗性去偏:该方法通过迭代训练一个分类器,以优化预测准确性,同时最小化对抗性模型从预测中推断受保护属性的能力。分类器旨在对目标变量进行准确预测,而对抗模型则试图辨别与偏差相关的敏感属性。这一同时训练的过程创造了一个竞争环境,使得分类器能够学习编码目标变量的重要信息,同时减少潜在偏见特征的影响。通过这种方式,对抗性去偏通过减轻敏感属性的影响并增强模型预测的整体公平性来促进公平性。
-
正则化:在深度学习中,正则化指的是对神经网络、数据或训练过程进行的任何添加或修改,旨在增加模型对外部数据的泛化能力。这可以间接帮助减少模型中的偏差。一些常见的正则化方法包括 dropout 层、L1/L2 正则化、批量归一化、组归一化、权重标准化、随机深度、标签平滑和数据增强。通过改善泛化能力,正则化方法有助于模型学习数据中的更一般性模式,而不是过度拟合训练集中的偏见特征。该方法在第二章中有更为详细的探讨,深度学习架构设计。
-
-
后处理:这是模型开发机器学习生命周期阶段的一部分,但仅涵盖处理已训练模型输出以减轻偏差。以下是属于该组的一些偏差缓解方法示例:
-
测试时反事实角色逆转增强集成:该方法涉及执行两个预测,每个使用相反的角色,并对预测结果进行集成。使用最大值或指数均值操作进行集成比平均操作效果更好,具体见
www.amazon.science/publications/mitigating-gender-bias-in-distilled-language-models-via-counterfactual-role-reversal。 -
均衡机会后处理:该方法通过通过阈值优化来调整分类器的输出预测,以确保不同组之间的假阳性和假阴性率相等,具体来说,分别为每个受保护组单独确定预测阈值。
-
需要注意的是,通过偏差缓解方法得到的更公平的模型可能会导致基于准确率的指标下降。如果在准确率指标上的损失足够小,那么公平的模型是非常值得追求的,因为使用偏差缓解方法不会带来问题。为此,始终从创建基线模型开始,并使用所选择的偏差和公平性指标评估公平性,以确保在使用任何偏差缓解方法之前,模型中确实存在偏差。偏差缓解方法总是会显著增加训练时间,因此确保它是值得的。
此外,还有一些与偏差相关的行为是有用的,需要了解:
-
决策算法往往偏向于更常见的事件,因此平衡的数据有助于减少这种偏差。
-
剪枝模型增加了偏差(更多内容请见
arxiv.org/pdf/2106.07849.pdf)。 -
容量有限的模型(较小的模型)往往会利用数据集中的偏差(
aclanthology.org/2022.gebnlp-1.27.pdf),因此在对较小模型进行知识蒸馏时需要小心,并评估偏差和公平性。
在缓解偏差时,您可能会想选择哪四类方法中的哪一类。以下是一个建议:最好在流程的尽早阶段解决偏差问题,这样您可以获得更多的控制权和灵活性。您不希望在缓解偏差时受到有限选项的困扰,最终无法令人满意地解决偏差问题。如果您无法访问数据收集阶段,您可以利用数据预处理技术。另一方面,如果您无法访问数据本身,但拥有已训练的模型,那么应该使用后处理技术。可以将多种技术结合使用,因此请务必使用引入的度量标准来衡量偏差,确保所应用的每种技术都能提高最终模型的公平性。同时,考虑使用多种方法来缓解更多偏差。
到目前为止,我们介绍的程序化偏差缓解方法分为三类。然而,深度学习模型中存在一种强有力的偏差缓解方法,它位于这三类方法的交集处。该方法融合了反事实数据增强、混合增强、知识蒸馏和反事实测试时增强。其思想如下:
-
使用反事实数据增强来训练教师模型和学生模型,涵盖整个数据集。
-
使用指数最大值对教师模型的反事实集成选择层特征进行蒸馏到学生模型。这类似于混合增强,但是在特征层面进行的。
然而,反事实角色互换仍然是更易于应用于文本的技术。让我们探索如何使用通用的知识蒸馏方法,减少在上一节中我们探索的人脸分类的同一用例中的偏差。我们在这里将介绍的方法来自arxiv.org/pdf/2112.09786.pdf,该方法提供了两种方法:
-
(蒸馏与去偏) D&D++:首先,使用仅限特权群体数据训练教师模型。然后,用教师模型的权重初始化学生模型,并使用正常的损失和基于所选特征层余弦相似度的知识蒸馏损失训练学生模型。最后,使用之前训练的学生模型初始化最终的学生网络,并在整个数据集上进行训练,以教师模型为来源,通过知识蒸馏从先前的学生模型中学习。
-
一步法蒸馏 D&D:为了简化流程,但仍然立即有效,步骤可以简化。首先,使用仅限特权群体数据训练教师模型。然后,使用教师模型的知识蒸馏,在整个数据集上训练学生模型。
让我们通过使用上一节中实验的用例,实际探索一步法蒸馏 D&D 方法:
-
首先,我们将使用仅限特权群体数据训练教师模型,该数据仅包含男性面部身份。让我们为此示例定义具体的不同配置:
batch_size = 64 lr = 0.05 num_epochs = 22 early_stopping_epochs = 12 reduce_lr_patience = 3 experiment_mlflow = "bias_mitigation_v2" logdir = 'experiments/face_modelv2' only_male=False distill_model=False -
我们将使用上一节中介绍的相同代码库,但会添加一些自定义代码。第一个是对第 6 步中评估深度学习模型的偏差与公平性部分代码的扩展,我们将移除训练和验证数据中的女性数据:
if only_male: train_raw_targets = targets[train_inds] val_raw_targets = targets[val_inds] train_male_inds = np.where(train_gender_data=='Male')[0] train_images = train_images[train_male_inds] train_targets = train_targets[train_male_inds] train_gender_data = train_gender_data[train_male_inds] val_male_inds = np.where( val_gender_data=='Male')[0] val_images = val_images[val_male_inds] val_targets = val_targets[val_male_inds] val_gender_data = val_gender_data[val_male_inds] -
第二是第 7 步,我们将定义一个特殊的前向方法和获取最后卷积特征的方法。我们的想法是确保关注的区域相同,而不是面部身份的概率,因为在模型作为人脸识别特征提取器时,这些概率是无用的。这个特殊的前向方法旨在一次前向传递中返回输出对数值和最后的卷积特征,从而减少延迟:
def get_last_conv_features(self, x): x = self.model.conv1(x) x = self.model.bn1(x) x = self.model.relu(x) x = self.model.maxpool(x) x = self.model.layer1(x) x = self.model.layer2(x) x = self.model.layer3(x) x = self.model.layer4(x) x = self.model.avgpool(x) x = torch.flatten(x, 1) return x def special_forward(self, x, targets=None): model_features = self.get_last_conv_features(x) outputs = self.model.fc(model_features) outputs = self.head(outputs, targets) return outputs, model_features -
接下来是第 8 步,在开始训练之前,我们将提取一次教师模型特征。这将减少训练学生模型所需的时间和资源,因为教师模型的特征将在相同的数据上保持固定,不做增强处理:
device = torch.device("cuda") if distill_model: if os.path.exists('all_teacher_model_features.npy'): all_teacher_model_features = np.load('all_teacher_model_features.npy') else: teacher_model = ARCResNet50(num_classes=num_classes) state_dict = torch.load(os.path.join(distill_model, "model.last.pth")) teacher_model.load_state_dict(state_dict) teacher_model.to(device) cpu_device = torch.device('cpu') entire_dataset = ImagesDataset(image_path, gender_data, targets=id_targets, transforms=get_transforms('valid')) entire_data_loader = DataLoader(entire_dataset, batch_size=val_batch_size, shuffle=False, num_workers=8) all_teacher_model_features = [] for batch in tqdm_notebook(entire_data_loader): inputs, sensitive_attribute, targets = batch with torch.no_grad(): outputs = teacher_model.get_last_conv_features(inputs.to(device)).to(cpu_device).numpy() all_teacher_model_features.append(outputs) del teacher_model all_teacher_model_features = np.vstack(all_teacher_model_features) np.save('all_teacher_model_features.npy', all_teacher_model_features) train_teacher_model_features = all_teacher_model_features[train_inds] val_teacher_model_features = all_teacher_model_features[val_inds] -
接下来,我们将定义训练和验证数据集,将训练和验证的教师模型特征分别输入到第 10 步中:
if distill_model: train_dataset = ImagesDataset( train_images, train_gender_data, train_targets, get_transforms('train'), train_teacher_model_features, ) val_dataset = ImagesDataset( val_images, val_gender_data, val_targets, get_transforms('valid'), val_teacher_model_features, ) -
最后的修改是通过处理批加载来添加额外的教师模型特征和损失,使用交叉熵损失和学生与教师模型最后一层卷积特征的余弦相似度损失,来自第 14 步:
if is_distill_mode: features, sensitive_attribute, targets, teacher_model_features = batch logits, child_model_features = self.model.special_forward(features, targets) loss = self.criterion(logits, targets) + nn.functional.cosine_similarity(teacher_model_features, child_model_features).mean() -
现在,按照前一节的步骤运行,进行更改后,你将获得一个大约为 0.774 的验证指标分数,所有其他分数为 0,因为在这第一步中没有女性。
-
要执行一步蒸馏的下一步骤,请再次执行之前的代码,但进行以下配置更改:
batch_size = 64 val_batch_size = 100 lr = 0.05 num_epochs = 150 early_stopping_epochs = 5 reduce_lr_patience = 2 experiment_mlflow = "bias_mitigation_v3" logdir = 'experiments/face_modelv3' only_male=False distill_model = 'experiments/face_modelv2' -
一步蒸馏 D&D 方法的验证得分为:AAOD 和 AOD 均为 0.3838,准确率为 0.76,差异影响为 0.395,统计公平性为 0.04627。为了公平比较,基础训练的准确率为 0.76333,AAOD 和 AOD 均为 0.38856,差异影响为 0.3885,统计公平性为 0.04758。这意味着改进主要来源于 AOD,差异为 0.0047,统计公平性差异为 0.00131。
-
通过再次使用第 19 步中的集成梯度代码,并应用于我们使用偏见缓解方法训练的模型,可以得到以下结果:

图 13.4:一步蒸馏 D&D 模型的解释
该模型现在专注于正确的特征——即无论性别如何,面部特征而没有背景或头发。
现在,自己尝试使用 D&D++,看看你能得到什么样的改进!
在本节中,我们发现了多种适用于神经网络模型的缓解技术,并间接地表明,评估指标并不是唯一的偏见指示器。
尽管本章主要关注深度学习模型和非结构化数据,但值得注意的是,我们讨论的概念、技术和指标同样适用于结构化数据。结构化数据中的偏见可能以类别不平衡、属性分布偏斜或样本不代表性等形式存在,并可能在模型的预测中体现出来,导致不公平的结果。一个显著的区别是,对于基于结构化数据的用例,偏见通常是由输入特征直接延续的。偏见和公平性评估方法,如基于均等表现的指标、基于均等错误的指标和分布公平性指标,可以用来评估在结构化数据上训练的机器学习模型的公平性。
总结
在本章中,我们专注于机器学习模型中的偏见和公平性这一关键问题。我们强调了部署偏见模型可能带来的负面后果,如法律诉讼和罚款。我们讨论了不同类型的偏见,并确定了在深度学习生命周期中偏见可能出现的阶段,包括规划、数据准备、模型开发和部署。
本章还介绍了几种用于检测和评估偏见与公平性的指标,包括基于平等代表性的指标、基于平等错误的指标、分布公平性指标和个体公平性指标。章节中提供了选择适当指标的建议,针对具体用例,并强调了在评估公平性时平衡不同观点的重要性,例如 WAE 和 WYSIWYG。本章还讨论了可以在模型构建的预处理、处理中和后处理阶段应用的程序性偏见缓解方法。这些方法的示例包括消除受保护属性、差异影响移除器、对抗性去偏和等化机会后处理。
最后,本章介绍了一种综合的深度学习模型偏见缓解方法,结合了反事实增强、混合增强、知识蒸馏和反事实测试时增强。这种方法旨在平衡模型的准确性性能和公平性要求。
通过这些内容,你已经了解了在机器学习模型中解决偏见与公平性的重要性和技术,以及如果不妥善处理可能带来的负面后果。这些知识将帮助你构建不仅在准确性指标上表现优异,而且考虑到伦理影响和公平性方面的机器学习系统,从而确保 AI 解决方案的负责任和有效部署,最终在现实应用中带来更好、更公平的结果。
随着我们前进,下一章将转向分析对抗性性能,这是确保生产中强健和可靠的机器学习模型的关键方面。
第十四章:分析对抗性能
在机器学习模型的背景下,对抗者指的是一个主动寻求利用或破坏这些模型的性能、完整性或安全性的实体或系统。他们可以是恶意行为者、算法或系统,旨在攻击机器学习模型中的漏洞。对抗者进行对抗攻击,故意输入误导性或精心设计的数据,欺骗模型并使其做出不正确或意外的预测。
对抗攻击可以从对输入数据的微小扰动到利用特定算法漏洞的复杂方法不等。对抗者的目标可能会根据不同的上下文有所变化。他们可能试图绕过安全措施、获得未经授权的访问权限、窃取敏感信息,或者破坏模型的预期功能。对抗者也可能瞄准机器学习模型的公平性和伦理,试图利用训练数据或模型设计中存在的偏见或歧视。一个例子是对抗者针对机器学习模型中的公平性和伦理问题进行攻击,尤其是在面部识别系统中。假设一个面部识别系统存在偏见,对男性的识别效果优于女性。对抗者可以通过故意改变外貌来利用这一偏见,误导系统。他们可能使用化妆、发型或配饰来混淆面部识别算法,使得系统更难准确识别他们。通过这样做,对抗者可以利用系统的弱点,可能逃避检测或误导执法部门的努力。
为了应对对抗者和对抗攻击,最好的第一步是分析训练好的机器学习模型的对抗性能。此分析有助于更好地理解模型中潜在的漏洞和弱点,从而能够开发出有针对性的缓解方法。此外,评估对抗性能还可以提供现有缓解策略有效性的洞察,并指导未来模型设计的改进。作为额外的好处,它有助于确保你的模型能够应对其部署环境中可能出现的任何自然变化,即使没有特定的对抗者针对该系统进行攻击。
本章将分别介绍基于图像、文本和音频数据的模型的对抗性能评估。具体而言,以下主题将被讨论:
-
使用数据增强进行对抗分析
-
分析基于音频模型的对抗性能
-
分析基于图像模型的对抗性能
-
探索基于文本模型的对抗分析
技术要求
本章包含一些在 Python 编程语言中的实际实现。要完成这些内容,您需要在计算机上安装以下库:
-
matplotlib -
scikit-learn -
numpy -
pytorch -
accelerate==0.15.0 -
captum -
catalyst -
adversarial-robustness-toolbox -
torchvision -
pandas
本章的代码文件可以在 GitHub 上找到:github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_14。
使用数据增强进行对抗性分析
对抗性性能分析方法的核心是利用数据增强。数据增强是指通过编程方式为现有数据引入现实的变化。数据增强通常在模型训练过程中使用,以提高验证性能和深度学习模型的泛化能力。然而,我们也可以将数据增强作为一种评估方法,以确保在不同条件下的性能鲁棒性。通过在评估过程中应用数据增强,实践者可以更全面、详细地估计模型在生产环境中部署后的表现。
对抗性性能分析提供了两个主要优点。首先,它有助于通过在训练过程中和训练后,在多个训练模型之间进行更好的模型选择,从而构建一个更具泛化能力的模型。这是通过使用增强前提指标实现的。某些使用场景可能具有特殊条件,这些条件不一定能代表原始验证集或保持分区中的数据。数据增强可以帮助改变评估数据集的表示,以模拟生产中的条件。其次,对抗性性能分析可以用于在模型部署到生产环境时建立有针对性的保障措施。通过彻底评估模型在不同对抗性条件下的表现,实践者可以设置具体的阈值、行动和操作指南,以确保模型的行为符合其要求。
选择所有可能的增强方式无疑有助于在不同条件下对性能进行预期对齐。然而,为对抗性分析精心选择的增强方式可以帮助您更有效地从这个过程中提取价值,而不仅仅是为了理解。以下是在使用增强进行对抗性性能分析时的一些建议:
-
考虑选择可以检测、测量和控制的增强方法:你是否拥有一个系统或机器学习模型,可以已经检测到增强方法可能改变的组件?拥有一个可测量的组件,并将其与所选增强方法进行对抗性分析,可以帮助在生产环境中设置实际的保护措施。保护措施可以从拒绝模型基于自动预测做出的决策,并将决策委托给人工审阅者,到要求使用机器学习模型的系统中的用户或参与者重新提交符合系统要求的输入数据。例如,在面部验证系统中设置一个保护措施,确保面部没有任何倾斜,保持正对。
-
考虑更可能在现实部署中发生的条件:通过专注于模仿现实条件的增强方法,实践者可以评估模型在与其预期部署相关的情况下的稳健性和表现。
-
在不同强度的增强方法下评估模型性能,以便更全面地理解性能:了解性能达到峰值和最低点的范围,将帮助你采取正确的行动。然而,某些增强方法仅有二元参数,如是否添加了增强效果,完全可以通过比较应用和不应用时的性能差异来进行评估。例如,是否应用了水平翻转。
-
考虑联合评估多种增强方法的性能:现实世界中的情况通常涉及多种因素,这些因素可能会影响模型的表现。通过同时应用多种增强方法来测试模型的表现,可以更好地了解模型处理复杂场景的能力,并识别出在单独评估单一增强方法时可能未显现的潜在弱点。
-
考虑使用流行的对抗性示例或生成对抗性示例的方法:利用知名的对抗性示例或技术,可以帮助你识别模型中可能被忽视的常见漏洞。通过识别并缓解这些漏洞,你已经防御了潜在攻击的相当一部分,因为这些流行方法更有可能被对手采用。
-
将具有目标特征的真实数据进行分组进行评估,比使用增强方法对对抗性性能分析更有效:有时候,增强方法无法正确复制现实生活中的情况,因此,收集并分析具有特定对抗特征的真实数据样本,可以提供对模型在现实世界场景中表现的更准确评估。
简而言之,评估可执行的增强方法。一般来说,具有价值的模型洞察是任何方式都能执行的。接下来,我们将通过第一个实际示例来进行基于音频的模型对抗分析。
分析基于音频的模型的对抗性能
基于音频的模型的对抗分析需要音频增强方法。在这一部分,我们将利用开源的audiomentations库来应用音频增强方法。我们将实际分析语音识别模型的对抗准确度表现。我们将使用的准确度指标是词错率(WER),这是自动语音识别和机器翻译系统中常用的指标。它通过计算单词替换、插入和删除的总和,再除以参考单词的总数,得出一个百分比值,从而衡量系统输出与参考转录或翻译之间的差异。WER 的公式如下:
WER = (S + I + D) / N
在这里,我们有以下内容:
-
S 代表单词替换的数量
-
I 代表单词插入的数量
-
D 代表单词删除的数量
-
N 是参考转录或翻译中的总单词数
以下增强方法将被考虑用于分析:
-
发音速度增强:改变单词发音的速度可以对 WER 产生显著影响。提高发音速度(时间压缩)可能导致更多错误,因为语音信息被压缩,而降低发音速度(时间扩展)可能会产生更准确的转录。由于单词的长短不同,每分钟音节数将是一个很好的估算指标,无需任何特殊的机器学习模型。
-
语音音调:改变语音音调可以影响口语单词的感知和识别。音调变化等增强方法可以引入音调的变化,进而影响 WER 性能。女性和男性的音调范围通常不同,这可以作为衡量音调的代理,因此我们不会在本主题中直接分析音调。音调可以通过机器学习模型或基于规则的科学方法来测量。
-
背景噪声:背景噪声的存在可能会对语音识别系统产生负面影响。背景噪声可以通过算法生成,如高斯噪声,也可以是通过有策略地选择现实环境中可能存在的背景噪声类型,如汽车或摩托车声音。然而,其存在不能简单地被检测,必须依赖于机器学习模型或手动环境控制。
-
语音响度/强度:语音的响度或音量在语音识别中起着至关重要的作用。增加或减少语音的响度可能引入反映现实条件的变化。常见的语音数据集是在没有任何外部噪声的封闭环境中收集的。这使得通过简单的数学方法就能轻松控制语音的响度。
图 14.1展示了四个组成部分的对抗性能分析图:语音速度性能分析图(a),高斯噪音性能分析图(b),语音响度性能分析图(c),以及现实生活背景噪音(摩托车噪音)性能分析图(d):

图 14.1 – 针对语音识别模型的对抗分析结果(WER)
在图 14.1(a)中,性能在每秒 2.5 到 4 个音节之间似乎是最佳的,并且男性似乎更容易在超出此范围时出现性能下降。在图 14.1(b)中,模型在每个 SNR 值大于 30 dB 时表现最佳。因此,一个简单的护栏就是确保语音始终比任何背景噪声大至少 30 dB,这可以通过硬件进行测量。在图 14.1(c)中,很明显,女性的表现无论语音的响度如何,都不会特别下降。然而,对于男性来说,最佳的表现可以在语音强度大约在 20 dB 范围内时获得,而不是超过这一范围。这显示了模型在性别上的某种偏差。图 14.1(d)显示了摩托车噪音与高斯噪音没有任何特殊的行为差异。考虑在实践步骤结束时评估更多你能想到的现实生活中的背景噪音!
执行语音识别模型的对抗性能分析
让我们从一个逐步的实际例子开始,展示如何获得图 13.1所示语音识别模型的对抗性能分析结果:
-
我们将使用来自 Hugging Face 开源平台的 Speech2Text 模型,这是一个基于变换器的英语语音训练语音识别模型。首先,导入必要的库,重点是
matplotlib用于绘图,numpy用于数组处理,pytorch用于处理 PyTorch Speech2Text 模型,audiomentations用于数据增强,Speech2Text 模型和 Hugging Face 的预处理器:import matplotlib.pyplot as plt import numpy as np import torch from tqdm import tqdm_notebook import evaluate import syllables from audiomentations import (AddBackgroundNoise, AddGaussianNoise, AddGaussianSNR, LoudnessNormalization, PitchShift, Shift, TimeStretch) from datasets import load_dataset from transformers import (Speech2TextForConditionalGeneration, Speech2TextProcessor) -
接下来,我们将加载已训练好的 Speech2Text 模型和预处理器,并将模型分配到 GPU 设备上:
device = torch.device("cuda") model = Speech2TextForConditionalGeneration.from_pretrained("facebook/s2t-small-librispeech-asr") processor = Speech2TextProcessor.from_pretrained("facebook/s2t-small-librispeech-asr") model.to(device) -
我们将用于评估模型的数据集是谷歌的英语语音识别
fleurs数据集,该数据集恰好包含了性别信息,允许我们间接评估音高表现差异,并进行偏差分析。让我们下载并加载该数据集:ds = load_dataset("google/fleurs", 'en_us', split="validation") -
接下来,我们将从 Hugging Face
evaluate库加载 WER 评估方法,并定义帮助方法从 Speech2Text 模型中提取 WER 分数:wer = evaluate.load("wer") all_gender = np.array(ds['gender']) gender_map = {'female':1, 'male':0} def get_wer_scores(dataset, transcriptions=None, sampling_rates=None, is_hg_ds=False): all_wer_score = [] for idx, audio_data in tqdm_notebook(enumerate(dataset), total=len(dataset)): inputs = processor( audio_data["audio"]["array"] if is_hg_ds else audio_data, sampling_rate=audio_data["audio"]["sampling_rate"] if is_hg_ds else sampling_rates[idx], return_tensors="pt" ) generated_ids = model.generate( inputs["input_features"].to(device), attention_mask=inputs["attention_mask"].to(device) ) transcription = processor.batch_decode(generated_ids, skip_special_tokens=True) wer_score = wer.compute( predictions=transcription, references=[audio_data['transcription'] if is_hg_ds else transcriptions[idx]] ) all_wer_score.append(wer_score) all_wer_score = np.array(all_wer_score) wer_score_results = {} for gender in gender_map.keys(): gender_idx = np.where(all_gender == gender_map[gender])[0] wer_score_results[gender + '_wer_score'] = all_wer_score[gender_idx].mean() wer_score_results['wer_score'] = all_wer_score.mean() return wer_score_results这里将返回三个分数,分别是男性特定分数、女性特定分数和总体分数。
-
接下来,我们将定义主要方法,应用增强到所有基准样本,并获取 WER 分数:
def get_augmented_samples_wer_results( all_baseline_samples, augment, transcriptions, all_sampling_rates ): all_augmented_samples = [] for idx, audio_sample in enumerate(all_baseline_samples): augmented_samples = augment(samples=audio_sample, sample_rate=all_sampling_rates[idx]) all_augmented_samples.append(augmented_samples) results = get_wer_scores( all_augmented_samples, transcriptions, sampling_rates=all_sampling_rates, is_hg_ds=False ) return results -
首先,我们将使用
audiomentation的时间拉伸方法分析发音速度组件的对抗性表现。数据集包含了每秒发音音节数不同的音频数据,因此在开始分析之前,我们必须确保所有音频数据每秒发音的音节数相同。我们先来计算每秒发音的平均音节数:all_syllables_per_second = [] for audio_data in ds: num_syllables = syllables.estimate(audio_data['transcription']) syllables_per_second = num_syllables / (audio_data['num_samples'] / audio_data['audio']['sampling_rate']) all_syllables_per_second.append(syllables_per_second) average_syllables_per_second = np.mean(all_syllables_per_second) -
现在,我们可以获取用于发音分析的初始基准音频样本集:
all_baseline_speed_audio_samples = [] transcriptions = [] all_sampling_rates = [] for idx, audio_data in tqdm_notebook(enumerate(ds), total=len(ds)): rate = average_syllables_per_second / all_syllables_per_second[idx] augment = TimeStretch(min_rate=rate, max_rate=rate, p=1.0) augmented_samples = augment( samples=audio_data['audio']['array'], sample_rate=audio_data['audio']['sampling_rate'] ) transcriptions.append(audio_data['transcription']) all_sampling_rates.append(audio_data['audio']['sampling_rate']) all_baseline_speed_audio_samples.append(augmented_samples) -
最后,我们将使用不同的加速和减速率进行对抗性 WER 分析:
rates = np.linspace(0.1, 1, 9).tolist() + list(range(1, 11)) wer_results_by_rate = [] for rate_to_change in tqdm_notebook(rates): augment = TimeStretch(min_rate=rate_to_change, max_rate=rate_to_change, p=1.0) results = get_augmented_samples_wer_results( all_baseline_speed_audio_samples, augment, transcriptions, all_sampling_rates ) wer_results_by_rate.append(results) -
运行以下绘图代码,你将得到如图 14.1(a)所示的图表:
labels = ["female", "male", "overall"] plt.xlabel("Syllables per second") plt.ylabel("WER") for idx, gender in enumerate(["female_", "male_", ""]): plt.plot( [average_syllables_per_second * i for i in rates], [wr[gender + 'wer_score'] for wr in wer_results_by_rate], label=labels[idx] ) plt.legend() -
现在,我们将进入下一个增强组件,该组件算法生成高斯背景噪声。在这个例子中,我们将控制信噪比(SNR)在语音信号和高斯噪声之间的关系。在这种情况下,原始数据可以作为基准,且无需进行均衡:
baseline_samples = [audio_data['audio']['array'] for audio_data in ds] snr_rates = np.linspace(1, 100, 25) wer_results_by_snr = [] for snr_rate in tqdm_notebook(snr_rates): all_augmented_samples = [] augment = AddGaussianSNR( min_snr_in_db=snr_rate, max_snr_in_db=snr_rate, p=1.0 ) results = get_augmented_samples_wer_results( baseline_samples, augment, transcriptions, all_sampling_rates ) wer_results_by_snr.append(results) -
运行以下绘图代码,你将得到如图 14.1(b)所示的图表:
plt.xlabel("SNR (dB)") plt.ylabel("WER") for idx, gender in enumerate(["female_", "male_", ""]): plt.plot( snr_rates, [wr[gender + 'wer_score'] for wr in wer_results_by_snr], label=labels[idx] ) plt.legend() -
接下来,我们将分析不同语音响度下的 WER 表现。来自
huggingface.co/datasets/google/fleurs的数据集是在没有背景噪音的封闭环境中制作的,因此可以通过以下代码直接计算原始语音音频数据数组的幅度:Wer_results_by_loudness = [] loudness_db = np.linspace(-31, 100, 25) for db in tqdm_notebook(loudness_db): augment = LoudnessNormalization( min_lufs_in_db=db, max_lufs_in_db=db, p=1.0 ) results = get_augmented_samples_wer_results(baseline_samples, augment, transcriptions, all_sampling_rates) wer_results_by_loudness.append(results) -
运行以下绘图代码,你将得到如图 14.1(c)所示的图表:
labels = ["female", "male", "overall"] plt.xlabel("SNR (dB)") plt.ylabel("WER") for idx, gender in enumerate(["female_", "male_", ""]): plt.plot( loudness_db, [wr[gender + 'wer_score'] for wr in wer_results_by_loudness], label=labels[idx] ) plt.legend() -
最后,我们将分析来自现实世界的真实背景噪音的对抗性表现。我们将使用来自
Freesound50k数据集的摩托车声音,并将其与原始音频数据在不同 SNR 下混合,使用以下代码:snrs = np.linspace(-50, 50, 20) wer_results_by_background_noise_snr = [] for snr in tqdm_notebook(snrs): augment = AddBackgroundNoise( sounds_path="motorbikes", min_snr_in_db=snr, max_snr_in_db=snr, p=1.0 ) results = get_augmented_samples_wer_results(baseline_samples, augment, transcriptions, all_sampling_rates) wer_results_by_background_noise_snr.append(results) -
运行以下绘图代码,你将得到如图 14.1(d)所示的图表:
plt.xlabel("SNR (dB)") plt.ylabel("WER") for idx, gender in enumerate(["female_", "male_", ""]): plt.plot( snrs, [wr[gender + 'wer_score'] for wr in wer_results_by_background_noise_snr], label=labels[idx] ) plt.legend()
在此基础上,我们尝试分析了语音识别模型的对抗性性能。可以考虑在你的端进行扩展分析,尝试联合评估多种增强方法。例如,考虑多个背景声音同时存在的场景。通过不同程度的背景声音增强音频,可以模拟不同干扰程度的场景。
在执行分析后,除了在训练过程中使用这些增强方法来缓解不良性能外,还可以在生产环境中添加保护措施。例如,考虑将语音识别输出作为输入传递给像 ChatGPT 这样的 LLM 模型,以防止内部错误结果的情况。一个保护措施的例子是,当检测到音频中的特定背景噪声时,重新路由系统,要求在将语音识别结果提交给 ChatGPT 之前进行人工验证。制定后续的可执行流程对于从洞察中释放价值至关重要。接下来,让我们探讨基于图像的模型的对抗性分析。
基于图像的模型的对抗性性能分析
基于增强的对抗性分析也可以应用于基于图像的模型。关键在于发现原本不存在的条件下,在验证数据集中可能导致准确度下降的因素。以下是一些可以通过增强方法评估的图像领域组件示例:
-
目标物体的大小:在使用闭路电视摄像头图像输入的应用场景中,对抗性分析可以帮助我们设置摄像头的适当距离,从而实现最佳性能。原始图像可以反复调整大小并覆盖在基础黑色图像上进行分析。
-
目标物体的滚动方向:俯仰和偏航方向不易增强。然而,旋转增强可以帮助对滚动方向的性能进行压力测试。任何姿态方向检测模型或系统都可以强制实现最佳性能。
-
模糊程度:图像可以被模糊化,OpenCV 库中有现成的图像模糊检测器可以使用。模糊增强可以帮助对该组件进行压力测试。
-
albumentations库提供了雨、雪、雾和阳光增强方法!
除了使用增强方法进行对抗性分析来评估不同条件下的性能外,还有许多其他广泛认可和广泛研究的方法,可以用于对基于图像的模型进行对抗性攻击。这里所说的“流行”也意味着这些技术对潜在攻击者容易获取,使他们能够轻松尝试这些方法。因此,必须彻底分析这些攻击,因为它们的发生概率和潜在影响增加了。
这些攻击试图通过优化以下之一来获得一个对抗性图像,从而欺骗模型:
-
一种图像扰动矩阵,它作为噪声混合器作用于原始图像,同时保持与原始图像的高度视觉相似性
-
一种可以数字化叠加在原始图像上的图像补丁,并能在现实世界中打印出来以规避检测或迷惑模型
图 14.2展示了两种针对对抗性攻击的优化方法示例:

图 14.2 – 对抗性图像补丁和对抗性图像噪声混合器示例
左上方的对抗性图像补丁是针对YOLOv2图像检测模型的,它作用于代表交通停止标志的类别,并能在现实世界中打印出来并贴在停车标志上时,使模型预测出其他随机类别。该补丁还能够将其对抗性特性转移到Faster-RCNN图像物体检测模型。左下方的对抗性图像补丁是通过优化来欺骗 YOLOv2 检测模型,使其在打印补丁贴在一个人身上时无法检测到人。右侧的对抗性图像噪声混合器示例展示了,当噪声添加到原始图像右侧时,生成的图像与原始图像在视觉上无法区分。ResNet50 模型(用于上一章构建模型时)准确预测了原始图像中的正确人物,但在添加了右上方图示中的噪声后,未能预测出正确的人物。图像补丁是基于实时计算机视觉应用的 CCTV 相关攻击。例如,窃贼希望阻止面部物体检测。对抗性图像噪声混合器适用于用户可以提供自己数据的用例。例如,社交媒体平台和媒体共享平台(如 Instagram 和 YouTube)希望通过机器学习来筛选和控制可以上传的媒体,而用户则希望绕过这些机器学习防护措施。
音频噪声混合器和音频补丁也可以用于对抗性攻击,针对基于音频的模型,方法类似于基于图像的模型。音频噪声混合器通过精心设计的噪声注入到原始音频信号中,生成一个对抗性示例,该示例在视觉上与原始音频保持高度相似,但却能欺骗模型。这在语音识别系统和音频内容过滤等应用中尤为相关,在这些应用中,对手可能会试图绕过安全措施或操控系统输出。另一方面,音频补丁涉及创建并将对抗性音频片段叠加到原始音频信号上。这些补丁可以设计为遮掩音频中的某些元素,或加入新的元素以欺骗模型。例如,对抗性音频可以通过任何音频设备自然播放在环境中,从而避开语音识别系统,或使语音识别模型误解特定的单词或短语。
这些技术是经过工程设计的对抗性攻击方法,可以分为两类:一种是需要访问神经网络梯度和模型本身的技术,另一种则只需要访问模型的预测概率或 logits 来优化图像。由于攻击者不太可能访问到神经网络梯度和模型本身,因此评估那些只需要预测概率或 logits 的对抗性图像生成方法更为实际。
攻击者可以选择随机生成噪声扰动矩阵,并希望它能成功欺骗模型。然而,一些算法可以自动优化生成有用的噪声扰动矩阵,只要你能访问到概率或 logits。其中一种只需预测 logits 就能生成欺骗模型所需噪声的算法叫做HopSkipJump算法。该算法旨在最小化生成有效对抗性示例所需的查询次数。以下是该算法的概述:
-
初始化:算法首先通过初始化目标类别和初始对抗性示例开始。
-
主循环:算法会反复执行一系列步骤,直到成功生成对抗性示例或达到预定义的查询限制:
-
跳跃:在这个步骤中,算法进行局部搜索,寻找一个扰动,使得初始对抗性示例更接近目标类别,同时确保扰动后的示例仍然是对抗性的。
-
跳过:如果跳跃步骤未能找到合适的扰动,算法将通过跳过一些像素来进行更广泛的搜索。这个步骤的目的是高效地探索更大的搜索空间。
-
跳跃:如果跳跃和跳过步骤都失败,算法将进行跳跃操作,对一小部分像素进行随机扰动,以鼓励探索不同的方向。
-
决策:在每个扰动步骤后(跳跃、跳过或跳跃),算法查询目标模型,以获取其对扰动示例的预测类别标签。
-
停止准则:如果算法成功生成对抗示例——即目标模型为扰动示例预测了目标类别——则算法终止。若查询次数超过预设阈值,算法也可以停止。
-
HopSkipJump 算法结合了局部和全局搜索策略,以高效地探索对抗示例的空间,同时最小化对目标模型的查询次数。这是一个可以在开源对抗攻击工具包中找到的多种攻击之一,地址为github.com/Trusted-AI/adversarial-robustness-toolbox。接下来,我们将逐步介绍如何获得在图 14.2中看到的对抗图像噪声混合结果等。
执行面部识别模型的对抗性能分析
在我们开始之前,请把自己置身于攻击者的角度和心态!步骤如下:
-
首先,我们将加载必要的库。重点是
albumentations用于数据增强,torch用于神经网络模型,art用于对抗示例生成算法:import os import albumentations as albu import numpy as np import pandas as pd import torch import torch.nn as nn from albumentations.pytorch.transforms import ToTensorV2 from PIL import Image from sklearn.model_selection import StratifiedShuffleSplit from torchvision import models from tqdm import tqdm_notebook import evaluate from art.attacks.evasion import HopSkipJump from art.estimators.classification import BlackBoxClassifier import matplotlib.pyplot as plt from catalyst.contrib.layers import ArcFace -
接下来,我们将重用我们在第十三章中构建的训练好的面部分类模型,探索偏差与公平性,它是一个以 ResNet50 为骨干的模型,结合了 ArcFace:
device = torch.device("cuda") class ArcResNet50(nn.Module): def __init__(self, num_classes): super(ArcResNet50, self).__init__() self.model = models.resnet50(pretrained=True) self.model.fc = nn.Linear(self.model.fc.in_features, self.model.fc.in_features) self.head = ArcFace(self.model.fc.out_features, num_classes, s=13, m=0.15) def forward(self, x, targets=None): output = self.model(x) outputs = self.head(output, targets) return outputs num_classes = 10000 model = ArcResNet50(num_classes=num_classes) model.to(device) model_path = '../CHAPTER_13/experiments/face_modelv10' state_dict = torch.load(os.path.join(model_path, "model.last.pth")) model.load_state_dict(state_dict) -
我们还将重用相同的数据集:
train = pd.read_csv('../CHAPTER_13/face_age_gender.csv') image_path = train['image_path'].values targets = train['target'].values name2class = {name: idx for idx, name in enumerate(sorted(set(targets)))} id_targets = np.array([name2class[target] for target in targets]) -
要开始使用来自
art库的HopSkipJump类,我们需要定义一个art库的黑箱分类器实例。该类是一个包装器,设置预期的生成图像噪声矩阵形状,并且仅能访问任何基于图像的模型的完全孤立预测方法。首先,我们准备好推理预测方法,以便 art 分类器可以使用它:transforms = albu.Compose([ albu.Resize(224, 224), albu.Normalize(), ToTensorV2() ]) def predict(x): if len(x.shape) == 3: img = transforms(image=x)["image"].unsqueeze(0).to(device) else: batch_img = [] for img in x: img = transforms(image=img)["image"].to(device) batch_img.append(img) img = torch.stack(batch_img) with torch.inference_mode(): output = model(img) return output.cpu().numpy() -
接下来,让我们从数据集中选择一个示例图像,并创建具有孤立预测方法、目标图像形状、类别数量和接受的像素值范围的黑箱分类器:
target_image = np.array(Image.open(os.path.join("../CHAPTER_13", image_path[1])).resize((224, 224))).astype(np.float32) classifier = BlackBoxClassifier( predict, target_image.shape, num_classes, clip_values=(0, 255)) -
现在,我们将使用分类器初始化
HopSkipJump攻击类,并将每次评估的最大迭代次数设置为10:attack = HopSkipJump(classifier=classifier, targeted=False, max_iter=10, max_eval=1000, init_eval=10) -
我们将运行该算法 30 次迭代。在每十次迭代的倍数时,我们将通过打印与原始图像的最小和最大像素差异、绘制生成的对抗图像并预测生成的图像来评估算法:
x_adv = None for i in range(3): x_adv = attack.generate(x=np.array([target_image]), x_adv_init=x_adv) print("class label %d." % np.argmax(classifier.predict(x_adv)[0])) plt.imshow(x_adv[0].astype(np.uint8)) plt.show(block=False) print(np.min(x_adv[0] - target_image), np.max(x_adv[0] - target_image)) -
最后,我们将绘制原始图像,获取模型在原始图像上的预测,并打印实际标签:
plt.imshow(target_image.astype(np.uint8)) print(np.argmax(classifier.predict(np.array([target_image.astype(np.float32)]))[0])) print(id_targets[1])结果显示在图 14.3中。你采取的步骤越多,生成的对抗图像与原始图像的视觉相似度就越高,同时仍能欺骗模型!这里的最小和最大像素差异提供了生成图像与原始图像相似度的一个参考,这将转化为视觉上的相似性:

图 14.3 – 单个面孔样本的对抗性能
仅凭模型的预测,我们成功地让模型错误地识别了一个面部身份!为了进一步推进,理解模型在扰动攻击中的脆弱性会很有帮助,可以通过检查更多的例子来深入了解。为了更广泛地基准化该方法,我们需要确保生成的对抗图像与原始图像在视觉上相似,这样从可能性角度来看才有意义。从图 14.3中的结果来看,可以合理推测,任何低于绝对最小和最大 10 像素差异的图像,都应保持足够的视觉相似性,能够让人类评估者相信图像没有问题,同时还能欺骗模型。我们将使用相同的 HopSkipJump 算法并进行 30 次迭代,但会添加一个条件:生成的对抗图像最大像素差异不超过 10 像素,并且与原始图像的最小绝对差异。如果不符合该条件,我们将使用原始图像的预测,视为未能生成有效的对抗攻击:
-
让我们首先随机选择 1,000 个分层样本,这样可以更快地进行评估:
splitter = StratifiedShuffleSplit(test_size=.02, n_splits=2, random_state = 7) split = splitter.split(image_path, targets) _, val_inds = next(split) val_inds = val_inds[:1000] -
接下来,我们将使用前述的工作流程计算预测,并对这 1,000 张图片使用原始图像:
all_adversarial_hsj_predicted_class = [] all_predicted_class = [] for idx, path in tqdm_notebook(enumerate(image_path[val_inds]), total=len(val_inds)): img = np.array(Image.open(os.path.join("../CHAPTER_13", path))).astype(np.float32) classifier = BlackBoxClassifier( predict, img.shape, num_classes, clip_values=(0, 255)) label = id_targets[idx] predicted_class = np.argmax(classifier.predict(np.array([img]))[0]) attack = HopSkipJump( classifier=classifier, targeted=False, max_iter=10, max_eval=1000, init_eval=10, verbose=False) x_adv = None for i in range(3): x_adv = attack.generate(x=np.array([img]), x_adv_init=x_adv) adversarial_hsj_predicted_class = np.argmax(classifier.predict(x_adv)[0]) if (np.min(x_adv - img) >= 10.0 or np.max(x_adv - img) >= 10.0): adversarial_hsj_predicted_class = predicted_class all_predicted_class.append(predicted_class) all_adversarial_hsj_predicted_class.append(adversarial_hsj_predicted_class) -
现在,让我们比较模型在原始图像和我们之前在s**tep 7中使用的对抗图像工作流程上的准确性表现。我们将使用 Hugging Face
evaluate库的准确性评估方法:accuracy_metric = evaluate.load("accuracy") print(accuracy_metric.compute(references=id_targets[val_inds], predictions=all_predicted_class) print(accuracy_metric.compute(references=id_targets[val_inds], predictions=all_adversarial_ba_predicted_class)结果是在原始图像上获得 56.9 的准确率,在对抗图像工作流程上则为 30.9 的准确率!
在这个实际示例中,我们成功使用了一个自动化算法来识别能成功欺骗我们在上一章构建的模型的对抗噪声混合器!
图像模型对对抗性噪声混合器类型和对抗性图像补丁类型极为敏感,因为这些情况与现实世界中的自然现象有很大偏离,模型可能难以正确处理。尽管机器学习模型擅长从真实世界数据中学习模式和特征,但它们通常难以应对显著偏离典型环境条件的合成扰动。这些对抗性技术利用图像模型的漏洞,导致它们误分类或产生错误的输出。因此,理解和解决这些漏洞在构建强大防御机制以应对此类攻击时至关重要。因此,考虑深入探索对抗性鲁棒性工具箱库,了解更多对抗性示例和攻击算法!为了减轻图像模型中的对抗性攻击,建议在训练过程中使用如高斯噪声和随机像素扰动器等对抗性目标增强。通过在训练过程中加入这些增强,模型可以学会更具抗扰动性,从而提高整体鲁棒性。
接下来,我们将深入探讨针对基于文本模型的对抗性攻击。
探索基于文本的模型的对抗性分析
基于文本的模型有时在处理特定单词、单词词干的特定词形变化,或相同单词的不同形式时,可能会存在性能漏洞。以下是一个例子:
Supervised Use Case: Sentiment Analysis
Prediction Row: {"Text": "I love this product!", "Sentiment": "Positive"}
Adversarial Example: {"Text": "I l0ve this product!", "Sentiment": "Negative"}
因此,对抗性分析可以通过对比在句子中添加重要单词与不添加重要单词时的性能来进行。为了减轻此类攻击,可以在训练过程中应用类似的单词替换增强。
然而,在现代基于文本的模型中,大多数广泛采用的模型现在依赖于预训练的语言模型基础。这使得它们即使经过领域微调后,仍然能够理解自然语言,因此,可以使用更复杂的对抗性攻击,利用自然语言欺骗。结果,彻底分析并开发出针对这些利用自然语言欺骗的复杂对抗性攻击的强大防御机制,对于确保现代基于文本的模型的可靠性和安全性至关重要。
可以把自然语言欺骗看作类似于人类通过自然语言进行欺骗的方式。正如人们可能会采取各种策略来误导或操控他人,比如社会工程学、上下文毒化和语言剖析,这些相同的方法也可以用来欺骗文本模型。以下是一个基于自然语言欺骗的例子,针对垃圾邮件/恶意邮件检测的机器学习使用场景:
Supervised Use Case: Spam/Malicious Email Detection
Prediction Row: {"Email Content": "Click the link below to claim your free iPhone.", "Label": "Spam"}
Adversarial Example: {"Email Content": "Hey, I found this article about getting a new iPhone at a great discount. Here's the link to check it out.", "Label": "Not Spam"}
社会工程学涉及使用心理操控来欺骗他人泄露敏感信息或执行特定操作。上下文污染是指故意引入误导性或无关的信息以混淆接收者。与此同时,语言剥削则利用语言的细微差别和模糊性来制造混乱或误解。通过理解和应对这些在人与人之间的欺骗技术,并将其应用于对抗性文本分析,我们可以增强文本模型对抗对抗性攻击的韧性,并保持其准确性和可靠性。不幸的是,目前并没有算法可以完全基准化这种完全改变句子的自然语言欺骗。我们需要依赖收集真实世界的欺骗数据来对这一部分进行对抗性分析。
然而,还有另一种值得一提的自然语言对抗性攻击形式,当前在攻击一般问题与答案的 LLM(如 ChatGPT)时越来越广泛使用。与重新格式化整个原始文本数据不同,额外的恶意上下文被用作原始文本数据的借口。LLM API 提供商通常会内置保护措施,以防止明显的、冒犯性的、令人反感的、歧视性的、个人攻击以及骚扰、暴力、非法活动、虚假信息、宣传、自残或任何不当内容。这是为了确保 AI 的负责任使用,并防止任何可能对个人和社会产生负面影响的行为。然而,一种名为“越狱”的流行对抗性攻击可以去除 ChatGPT 所实施的所有内容生成限制。越狱攻击方法是一种经过工程设计的提示,已经公开分享,并可以作为任何实际用户提示的前置条件。许多版本的这种工程化提示已被公开分享,并且可以通过谷歌搜索轻松找到,从而使世界上的每个人都能攻击 ChatGPT。幸运的是,OpenAI 一直在尽力减少这种越狱对抗性攻击,每当他们获得这些提示时都会及时应对。新的越狱提示版本更新非常频繁,OpenAI 也因此陷入了一个不断循环的过程,试图应对新的工程化越狱攻击。
总之,进行对抗性分析对于增强文本模型对欺骗技术和攻击的韧性至关重要。通过理解人类欺骗行为并保持警觉以应对不断变化的威胁,我们可以在超越单纯的在精心设计的测试数据上取得准确性的基础上,提升这些文本模型的可靠性和安全性。
总结
本章介绍了机器学习模型的对抗性性能分析的概念。对抗性攻击通过故意输入误导性或精心设计的数据来欺骗模型,导致错误的预测。本章强调了分析对抗性性能的重要性,以识别机器学习模型中的潜在脆弱性和弱点,并开发针对性的缓解方法。对抗性攻击可以针对机器学习模型的各个方面,包括它们的偏见和公平性行为,以及基于准确度的性能。例如,面部识别系统可能会成为对手的攻击目标,攻击者利用训练数据或模型设计中的偏见或歧视。
我们还探讨了分析图像、文本和音频数据模型对抗性性能的实际例子和技术。对于基于图像的模型,讨论了对象大小、方向、模糊度和环境条件等各种方法。我们还实际探索了一种算法方法,该方法用于生成噪声矩阵,以便混合并扰乱原始图像,并生成能够欺骗训练好的面部分类器模型的对抗性图像;这一方法来自于第十三章,探索偏见与公平性。对于基于音频的模型,分析了如发音速度、语音音调、背景噪音和语音响度等增强方法,而对于基于文本的模型,探索了基于单词变化的攻击、自然语言欺骗和越狱攻击。
总之,对抗性分析对于增强机器学习模型抵御欺骗技术和攻击的韧性至关重要。通过理解人类欺骗行为,并保持对不断发展的对抗性威胁的警觉,我们可以提高神经网络模型的可靠性和安全性。
在下一章,我们将进入深度学习生命周期的下一阶段,探索深度学习模型在生产中的应用世界。
第三部分 – DLOps
在本书的这一部分,你将深入了解在生产环境中部署、监控和治理深度学习模型的激动人心领域,并与 MLOps 和 DevOps 进行类比。本部分将为你提供关于确保深度学习模型在生产中成功应用和实际影响所需的核心组件的全面理解。
在本部分的各章中,我们将探讨将深度学习模型部署到生产环境中的各个方面,涵盖诸如硬件基础设施、模型打包和用户界面等重要考虑因素。我们还将深入讨论模型治理的三大支柱:模型利用、模型监控和模型维护。你将了解漂移的概念以及它对已部署深度学习模型性能的长期影响,并学习如何有效应对漂移。我们还将讨论像 DataRobot 这样的 AI 平台的好处,它们简化了机器学习生命周期中的复杂阶段,加速了复杂深度学习模型的创建、训练、部署和治理。
作为额外内容,基于第六章《理解神经网络变换器》中的基础变换器方法,我们将深入探讨大型语言模型(LLM)解决方案,这些解决方案正在革新各个领域。你将学习如何架构 LLM 解决方案并构建自主智能体,掌握利用其潜力所需的知识。
本部分包含以下章节:
-
第十五章《在生产环境中部署深度学习模型》
-
第十六章《深度学习模型治理》
-
第十七章《在动态环境中有效管理漂移效应》
-
第十八章《探索 DataRobot AI 平台》
-
第十九章《架构 LLM 解决方案》
第十五章:将深度学习模型部署到生产环境
在前面的章节中,我们深入探讨了数据准备、深度学习(DL)模型开发的复杂性,以及如何从我们的 DL 模型中获得有价值的见解。通过细致的数据分析、特征工程、模型优化和模型分析,我们已经掌握了确保 DL 模型表现良好并按预期运行的技术。随着我们进入下一个阶段,焦点将转向在生产环境中部署这些 DL 模型。
将深度学习模型部署到生产环境的阶段是一个重要的成就,因为大多数模型无法走到这一步。如果你的项目已经达到了这一里程碑,这意味着你已经成功满足了利益相关者的需求,提供了有价值的见解,并进行了全面的价值和指标分析。恭喜你,你离加入成功项目的少数群体又近了一步。值得注意的是,根据 2022 年 Gartner 的一项调查,VentureBeat 进行了报道,这项调查在线进行,时间为 2021 年 10 月至 12 月,收集了来自美国、德国和英国的 699 名受访者的数据,约有一半(54%)的 AI 模型最终进入生产阶段。此外,由 AI 资源管理解决方案提供商 Run AI 发布的 2023 年《AI 基础设施状态调查》报告显示,在超过 88%的受访公司中,进入生产阶段的 AI 模型不到一半。该调查涉及 450 名来自美国和西欧的行业专业人士。这两项调查突显了这个过程中的挑战,以及达到这一阶段的重要性。
这里的最终目标是让这些 DL 模型以直观的方式对最终用户可用,使他们能够在实际应用中充分发挥 DL 的潜力。在本章中,我们将探讨将 DL 模型无缝集成到生产系统中的各种策略、工具和最佳实践,确保可扩展性、可靠性和用户友好性,适应各种不同的用户需求。
具体而言,我们将讨论以下主题:
-
探索深度学习模型部署的关键组件
-
确定深度学习模型部署的关键需求
-
选择正确的深度学习模型部署选项
-
探讨基于实际使用案例的部署决策
-
探索深度学习(DL)模型部署的通用建议
-
使用 ONNX、TensorRT 和 NVIDIA Triton 服务器部署语言模型
技术要求
在本章的最后一部分,我们将进行一个实践性主题。本教程要求你拥有一台配备 NVIDIA GPU 设备的 Linux 机器,最好是在 Ubuntu 上安装 Python 3.10 和nvidia-docker工具。此外,我们还需要安装以下 Python 库:
-
numpy -
transformers==4.21.3 -
nvidia-tensorrt==8.4.1.5 -
torch==1.12.0 -
transformers-deploy -
tritonclient
代码文件可在 GitHub 上获取:github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_15。
探索深度学习模型部署的关键组件
那么,部署深度学习模型需要什么?它从全面了解每个必需的组件开始,并定义清晰的需求,以指导每个方面的决策。这种方法确保与业务目标和需求保持一致,最大化成功部署的机会。通过精心规划、严谨执行和专注于满足业务需求,你可以增加成功部署深度学习模型并为用户释放其价值的可能性。我们将从探索部署深度学习模型所需的组件开始。
将深度学习模型部署到生产环境不仅仅涉及训练好的模型本身。它需要各个组件之间的无缝协作,共同工作以帮助用户有效地从模型的预测中提取价值。以下是这些组件:
-
架构选择:部署系统的整体设计和结构。模型应该作为一个独立的服务、微服务,还是直接作为现有服务的一部分来实现?模型应该托管在云端还是本地?另一个需要考虑的方面是,是否使用容器编排平台,如 Kubernetes、Docker Swarm 或 Apache Mesos,来管理和扩展深度学习模型在容器化应用中的部署。
这些平台提供灵活的部署方式,可以跨多台机器、云服务提供商或本地基础设施进行部署,并可与其他工具和服务结合使用,以高效管理容器化应用程序和微服务。
-
硬件/物理基础设施选择:这涉及到选择你希望使用的物理计算设备,以及组成该计算设备的各个组件的选择。模型应该在 CPU、GPU、TPU,还是 iPhone 中的 人工神经引擎(ANE)上运行?
-
模型打包方法和框架:这是一个涉及将模型的架构、权重和配置序列化为文件或容器格式的组件,从而实现模型在各种环境中的分发、部署和使用。通常,深度学习框架会提供开箱即用的模型打包支持。你是否有架构选择和硬件基础设施偏好,要求模型以特定方式进行打包?
-
模型安全性、可信度和可靠性组件:这包括采取措施确保部署的模型在做出准确预测时是安全、可信和可靠的。它涉及到实施防护措施以防止滥用或意外行为,确保模型的一致性,监控模型性能,并提供预测解释,以帮助用户理解和信任模型的输出。确保数据隐私并遵守相关法规也是该组件的一个关键方面。是否有任何特定的安全性、可信度或可靠性要求,必须在你的深度学习模型部署中得到满足?
-
安全性和身份验证方法:这些涉及到保护你的深度学习模型及其相关基础设施,以及通过实施合适的身份验证、授权和加密机制来控制对模型的访问。这确保只有授权用户才能访问和与模型进行交互,防止未授权访问、数据泄露以及潜在的模型滥用。你的深度学习模型部署需要哪些安全性和身份验证要求?这些要求将如何集成到你的系统中?
-
通信协议:这些定义了在部署的模型和系统中的其他组件或用户之间交换数据的规则和格式。它涉及根据要求选择适当的协议,例如延迟、可靠性和数据格式。通信协议的示例包括 HTTP、RESTful API、gRPC、服务器推送事件和 WebSocket。哪些通信协议最适合你的深度学习模型部署?它们将如何实现,以便模型和用户之间能够无缝互动?
-
用户界面:这些是视觉组件和交互方式,允许用户或下游系统访问、与部署的深度学习模型进行交互并获取预测。用户界面可以是基于网页、移动端、桌面应用程序、API,甚至是语音激活系统,具体取决于使用场景和目标用户群体。设计用户友好且直观的界面至关重要,以确保用户能够轻松理解并充分利用模型的预测功能。什么样的用户界面最适合你的深度学习模型部署?如何设计界面,以提供最佳的用户体验,同时有效地传递模型的能力?以下是一些特定于深度学习模型的用户界面设计挑战示例:
-
可视化复杂数据:深度学习模型通常处理多维数据,这可能会让数据以用户友好的方式展示变得具有挑战性。设计人员可能需要设计创新的方式来可视化和呈现这些数据,使用户能够访问并理解这些数据。
-
处理实时数据:在深度学习模型处理和分析实时数据的场景中,用户界面必须有效地管理数据流和更新,确保用户及时获得准确的信息,同时避免信息过载。
-
促进模型交互:用户可能需要与深度学习模型互动,以调整参数、提供反馈或请求额外信息。设计直观的用户界面元素以支持这些交互至关重要,确保用户能够有效地与模型互动。
-
解释模型输出:深度学习模型可能会产生复杂且微妙的输出,这可能会使用户理解和操作变得困难。设计者必须找到方法,以清晰且可操作的方式呈现模型预测结果,同时提供上下文信息,帮助用户解释结果。
-
管理不确定性:深度学习模型可能会产生具有不同置信度或不确定性的预测。设计者应考虑如何将这种不确定性传达给用户,确保他们意识到模型输出的局限性和潜在风险。
-
可访问性与包容性:深度学习模型的用户界面应当适应各种不同背景的用户,包括有不同能力、语言和文化背景的用户。设计师必须确保他们的界面具有可访问性和包容性,考虑到各种用户需求和偏好。
-
-
监控与日志组件:这些工具允许你实时跟踪深度学习模型的性能、使用情况和健康状况。通过收集和分析相关的指标、日志和警报,该组件有助于发现潜在问题、优化模型性能,并确保稳定的部署环境。你将如何实施监控和日志记录,以追踪深度学习模型的健康状况和性能,并且哪些指标对衡量其成功至关重要?
-
持续集成/持续部署 (CI/CD):该过程涉及每当模型的代码、数据或基础设施发生变化时,自动构建、测试和部署你的深度学习模型。CI/CD 简化了开发生命周期,实现了更快速的迭代和改进,同时确保已部署的模型保持最新并且可靠。你将采用哪些 CI/CD 实践和工具,以维持你的深度学习模型的无缝部署流程?
在每个组件都有众多选项的情况下,制定决策策略至关重要。这个过程的第一步是定义具体的需求,以指导每个组件的决策。在下一节中,我们将讨论如何建立这些需求,确保你的选择与业务目标相符。
确定深度学习模型部署的关键要求
为了从多种选项中确定最合适的部署策略,必须识别并定义七个关键要求。这些要求包括延迟和可用性、成本、可扩展性、模型硬件、数据隐私、安全性,以及信任和可靠性要求。让我们详细探讨每一个要求:
-
延迟和可用性要求:这两者是紧密相关的组件,应一同定义。可用性要求指的是模型预测的期望运行时间和可访问性水平。延迟要求指的是模型必须满足的最大可接受延迟或响应时间,以提供及时的预测或结果。如果一个部署具有较低的可用性要求,通常可以容忍较高的延迟预测,反之亦然。原因之一是,如果低延迟基础设施在请求模型预测时不可用,那么它就无法确保低延迟。然而,也有一些特殊情况,只在短时间内需要完全可用且低延迟,而其他时间可以不可用,这被视为低可用性但具有低延迟要求。以下是确定延迟和可用性要求时的一些最佳实践:
-
考虑使用 DL 模型的最终用户或应用程序的期望和需求。与相关方沟通,了解他们期望的响应时间和可用性水平。
-
评估延迟和可用性对整体系统或业务流程的影响。识别延迟可能显著影响用户体验或业务运营的关键点。等待至少 1 小时才能获得预测,是否能够提供业务所需的价值?
-
确定可用性特别重要的时间窗口或时段。确定 DL 模型是否需要全天候 24/7 可用,或是否有特定的时段或事件需要高可用性。
-
设置理想和最大延迟与可用性阈值。最大值通常是指仍能获得显著价值的水平,而理想条件则是稍微提高该值的情况。
-
-
成本要求:预算限制是任何企业的关键考虑因素,因此必须确定您愿意为部署机器学习模型分配的最大成本,以便根据模型预期带来的价值进行规划。为了确保费用不会超过组织愿意投入的金额,建议进行成本效益分析。此分析将评估部署基础设施中各个组件的成本影响,包括如何实现更高的延迟和可用性水平。通过仔细平衡所需要求与相关基础设施成本及操作复杂性,您可以做出与组织整体财务目标相一致的明智决策,同时依然能利用机器学习的优势。
-
可扩展性要求:可扩展性是指部署基础设施能够在不影响性能或质量的情况下,应对工作负载需求的增加或减少。确定深度学习模型的可扩展性要求至关重要,因为这将影响部署策略和基础设施的选择。您是否预期模型使用量会随着时间增长?您预期它会增长多快?您需要横向扩展(增加更多模型实例)还是纵向扩展(增加现有实例的资源)?对使用增长速率的预期将帮助您在模型落地时做出适当的组件选择和决策。
-
模型硬件要求:选择用于部署深度学习模型的硬件至关重要,因为它会显著影响整体部署的性能、延迟和成本。为了正确识别硬件需求,请考虑以下几点:
-
兼容性:确保所选硬件与用于开发深度学习模型的框架和库兼容。这包括检查硬件是否能支持特定功能,例如可能对模型性能至关重要的 GPU 加速。
-
处理能力:评估高效运行模型所需的处理能力,包括核心数量、内存和存储。考虑模型的复杂性和大小可能对硬件要求的影响。
-
功耗和散热:所选硬件的功耗和散热情况会影响整体运营成本和部署的环境足迹。选择能在性能与能源效率之间找到平衡的硬件。
-
未来适应性:考虑硬件的预期使用寿命及其是否能够适应模型未来的更新或改进。选择可以轻松升级或更换的硬件,以应对可能的需求。
-
集成:确保硬件能够无缝集成到其他部署基础设施及相关系统或组件中。
通过彻底评估模型的硬件要求,您可以做出明智的决策,确保在优化性能的同时,最小化成本和潜在的瓶颈。
-
-
数据隐私要求:确保深度学习模型及其预测所使用数据的隐私和安全至关重要,因为这会影响部署的信任和合规性。为识别并解决数据隐私要求,请考虑以下方面:
-
合规性要求:了解适用于您组织的数据保护法规和行业标准,如 GDPR、HIPAA 或 CCPA。确保部署策略和基础设施符合这些法规。
-
数据存储和处理地点:评估数据在部署过程中存储和处理的位置。确定是否存在数据驻留要求或限制,如需要将数据存储在特定的地理区域。
-
数据访问控制:所有深度学习应用都应要求实施适当的访问控制,以确保只有授权的用户或系统可以访问数据。这包括实现身份验证、授权和加密机制。
-
数据保留和删除政策:检查是否有数据保留和删除的法律和合规要求。确保部署基础设施支持这些政策,并在必要时允许安全删除数据。
-
数据监控与审计:检查是否需要实施监控和审计机制,以跟踪部署过程中数据的使用和访问。
-
数据泄露响应计划:此类计划应包括角色与职责、沟通渠道和修复措施。检查是否需要制定数据泄露响应计划,列出发生数据泄露或安全事件时的应对步骤。
-
-
安全性要求:反思模型在特定地区部署时必须遵守的法律和伦理边界。
-
信任与可靠性要求:机器学习模型的信任与可靠性指的是对模型在部署和运行过程中保持一致的性能、准确性,以及遵守伦理和合规标准的信心。在确定要求时,请考虑以下问题:
-
模型多久更新或修改一次?
-
是否有必要追踪多个模型版本?
-
模型在其运行环境中是否会面临概念漂移或数据漂移?
-
高效的错误检测和解决有多重要?
-
模型多久会收到更新或新增功能?
-
适应用户反馈或变化的需求是否至关重要?
-
是否有机会利用深度学习的进展来改进模型?
-
维护稳定和安全的生产环境是否是优先事项?
-
模型的性能对其用户或业务功能有多关键?
-
是否有与性能相关的严格服务水平协议(SLA)或监管要求?
-
在不同环境和配置中,性能一致性是否重要?
-
模型的预测是否具有重大影响,从而使得一致性对用户信任和成功至关重要?
-
其中一些要求最好在规划阶段早期确定。例如,从一开始就定义延迟要求,可以帮助您选择一个合适的模型,以确保运行时持续时间符合指定的延迟限制。在探讨了需要定义的要求类型和定义这些要求的大致方法后,我们现在可以讨论如何选择正确的部署选项。
选择合适的深度学习(DL)模型部署选项
为深度学习(DL)模型选择正确的部署选项是确保最佳性能、可扩展性和成本效益的关键步骤。为了帮助您做出明智的决策,我们将根据不同的需求探讨推荐的选项。这些建议涵盖多个方面,例如硬件和物理基础设施、监控和日志记录组件,以及部署策略。通过仔细评估模型的特性、资源限制和预期结果,您应该能够识别最合适的部署解决方案,以最大化效率并通过本指南实现投资回报率。我们将在这里探讨的实际部署组件包括架构决策、计算硬件、模型打包与框架、通信协议和用户接口。让我们一一深入探讨每个组件,从架构选择开始。
架构选择
机器学习服务的架构选择涉及设计基础设施、数据管道和部署方法,以确保高效和可靠的操作。我们将从服务部署的考虑因素开始:
-
微服务:将深度学习(DL)模型部署为一个小型、松散耦合、可独立部署的服务,并拥有自己的 API。微服务是一种软件架构设计模式,将应用程序构建为一组小型、松散耦合且可独立部署的服务。每个微服务负责应用程序中的特定功能或领域,并通过明确定义的应用程序接口(API)与其他微服务进行通信。因此,当以微服务形式部署时,前提是其他组件也必须以微服务的方式实现。其优势如下:
-
更好的可扩展性
-
更便捷的更新和维护
-
更高的弹性
-
技术选择的灵活性
在以下情况下选择该微服务:
-
当预计模型使用量会增长时
-
当需要频繁更新时
-
当需要与各种外部系统集成时
-
当高可用性至关重要时
-
-
独立服务:将深度学习模型作为一个独立的服务部署,而不是微服务。以电影推荐应用为例——微服务方法是创建一个评论分析服务微服务,使用深度学习模型处理电影评论。它有自己的 API、数据存储和部署管道,并且独立于应用中的其他服务运行。在同一个应用中采用独立服务方法,电影推荐服务结合了用户偏好管理、电影评论分析(使用深度学习模型)和推荐生成。它更具单体结构,整合了相关功能,拥有自己的 API,但没有为评论分析单独设置微服务。其优点如下:
-
更容易的管理和维护
-
更适合复杂的应用
-
集中的资源和数据访问
-
组件之间的简化通信
-
更可预测的性能
在以下情况下选择此独立服务:
-
当应用中服务数量有限时
-
当模型复杂并且需要更单体化的方法时
-
当模型的范围变化不频繁时
-
当优先考虑弹性与复杂性之间的平衡时
-
-
现有服务的一部分:将深度学习模型集成到应用或系统的现有服务中。其优点如下:
-
更少的复杂性
-
改进的性能
-
更容易的数据同步
-
潜在的成本节约
在以下情况下选择与现有服务集成:
-
当模型使用量增长有限时
-
当不需要频繁更新或修改时
-
当与外部系统的集成有限时
-
当高弹性不是关键要求时
-
在微服务和与现有服务集成之间做出决定时,应考虑可扩展性、更新频率、集成需求和弹性等因素。根据这些因素与您的具体需求对齐,以便做出最佳的深度学习模型部署决策。接下来,我们将讨论选择物理部署环境的建议:
-
云:当您需要高可用性并且能容忍适度的延迟时,云部署是合适的。它们减少了前期成本,并提供灵活的按需付费定价模型。基于云的基础设施提供几乎无限的资源,允许快速自动扩展,并且通常提供高可用性的保证和托管服务。然而,您需要仔细评估云服务提供商的安全性,并确保其与您的深度学习框架和库兼容。提供 GPU 服务的公司包括 AWS、GCP、Microsoft Azure 和 IBM Cloud。
-
本地服务器:本地服务器部署让你对硬件和网络资源拥有更多的控制权,非常适合在特定地理区域内实现低延迟和高可用性。这类部署需要在硬件和维护方面的前期投资,但如果你有高且稳定的资源需求,长期来看可以节省成本。与云端部署相比,本地部署在安全措施和数据隐私方面提供更多的控制权,但也需要更多的精力来维护和更新安全措施。确保与深度学习框架及库的兼容性。
-
边缘本地部署:也叫做边缘计算,这种方法将数据处理靠近数据源,提供极低的延迟、提升的安全性和数据隐私保护。边缘部署适用于需要将数据处理和存储靠近数据源的场景,并且能够减少数据传输成本。然而,跨多个边缘设备管理安全性,并确保与深度学习框架及库的兼容性,可能是一个挑战。边缘部署在分布式处理方面具有可扩展性,但可能需要更多的管理和维护工作。
接下来,我们将深入探讨容器编排平台,它们对应用程序和服务在系统中的设计、部署和管理方式产生了深远影响。容器是一个轻量级、独立的可执行软件包,包含了运行软件所需的所有内容,包括代码、运行时、系统工具、库和设置。容器彼此隔离,也与主机系统隔离,使它们能够在不同的计算环境中一致地运行。容器技术主要有两种类型:Docker 容器和 Linux 容器(LXC)。
容器编排平台帮助管理和扩展容器化应用程序中深度学习模型的部署,利用 Docker 容器或 LXC 等技术。这些平台提供跨多台机器、云服务商或本地基础设施的灵活部署。它们可以与其他工具和服务结合使用,从而实现对容器化应用和微服务的高效管理。以下是一些流行的容器编排平台:
-
Kubernetes(开源):Kubernetes 是一个开源的容器编排平台,能够自动化部署、扩展和管理容器化应用程序,包括深度学习模型。它与多种容器技术兼容,包括 Docker 和 LXC。
-
Docker Swarm(开源):Docker Swarm 是 Docker 容器的原生集群和调度工具。它与 Docker 生态系统紧密集成,提供了一种简单的方式来部署和管理容器化应用程序。虽然功能上不如 Kubernetes 丰富,Docker Swarm 以其易用性和更快速的设置而闻名。
-
Apache Mesos(开源):Apache Mesos 是一个分布式系统内核,它将 CPU、内存和存储资源从机器中抽象出来,使得系统具有容错性和弹性。它可以与其他框架(如 Marathon 或 DC/OS)结合使用,提供容器编排功能,用于部署和管理深度学习模型。
-
Amazon Elastic Kubernetes Service (EKS) 和 Amazon Elastic Container Service (ECS)(收费服务):这些是 AWS 提供的托管容器编排服务。EKS 是一个托管的 Kubernetes 服务,而 ECS 是 AWS 自有的容器编排平台。这两项服务简化了在 AWS 基础设施上部署、扩展和管理容器化应用程序的过程。
选择最适合你深度学习部署需求的容器编排平台,例如灵活性、可扩展性、与你偏好的容器技术的兼容性、云提供商以及与其他工具和服务的集成。
接下来,我们将深入探讨实时预测和批量预测之间的架构权衡:
-
实时预测:建议将模型始终加载到内存中,以减少延迟并快速响应请求。此设置适用于即时响应至关重要的应用场景,如自动驾驶汽车、实时聊天机器人或欺诈检测系统。使用此选项时,以下是一些建议:
-
使用一台专用服务器或云实例,确保有足够的内存和处理能力来处理模型和并发请求
-
使用量化、剪枝或模型蒸馏等技术优化模型推理
-
如果需要,实施负载均衡器以将传入请求分配到多个模型实例
-
监控资源使用情况和性能,确保系统满足实时要求,并根据需要进行扩展
-
使用队列系统确保工作人员不会过载,或者实施自动扩展来处理超载情况
-
-
按需批量预测:批量预测适用于实时响应不重要的场景,且预测可以按批次处理。此设置需要额外的时间来启动工作基础设施、初始化模型并加载训练好的模型权重。使用此选项时,以下是一些建议:
-
使用队列系统,如 RabbitMQ 或 Amazon SQS,来管理传入的预测请求
-
设置批处理系统,当处理开始时初始化模型并加载权重
-
优化批处理大小,以平衡处理时间和资源使用
-
实施自动扩展来处理变化的工作负载并确保资源的高效使用
-
接下来,我们将探讨计算硬件选择和建议。
计算硬件选择
选择硬件以执行模型计算是关于成本、可用性和运行时之间的权衡。我们来探讨不同的选项,并附上使用每种选项的建议:
-
CPU:CPU 是部署深度学习模型的多功能且具有成本效益的选项。它们与大多数框架和库兼容,并为不太复杂的模型提供不错的性能。当成本是首要考虑因素,并且你不需要 GPU 或 TPU 提供的高处理能力时,CPU 是一个不错的选择。
-
GPU:GPU 提供更快的处理速度和更好的并行化,显著降低延迟并提高性能。它们非常适合需要高处理能力的复杂模型。当你需要低延迟和高可用性时,GPU 是一个极好的选择,但它们的成本高于 CPU。
-
TPU:TPU 是为机器学习任务设计的专用硬件,提供高性能和高效处理。它们特别适用于大型模型或计算密集型任务。当你需要卓越的处理能力和低延迟时,TPU 是一个不错的选择,但要注意可能会有较高的成本,而且它仅在 GCP 中可用,并且只能在 TensorFlow 中使用!
-
人工神经引擎 (ANE):ANE 是专门的 AI 加速器,通常出现在如 iPhone 等设备中。它们为边缘设备上的深度学习任务提供高效的处理,具有低延迟和能效高的性能。当你的应用需要在 iPhone 这样的边缘设备上满足用户界面需求时,ANE 是一个不错的选择。需要注意的是,它仅与 CoreML 框架兼容,且需要使用 ONNX 权重格式来轻松转换权重到 CoreML。
-
FPGA:FPGA 是高度可定制的(其硬件电路可以编程!),同时具有能效高的特性,适用于部署需要低延迟和适应性的深度学习模型。这里的缺点是需要具备深厚的 FPGA 编程语言和电路开发专业知识,才能成功地高效地进行已训练神经网络的推理。这对于大多数团队来说是一个超出范围的设备。
接下来,我们将探讨模型打包和框架选择以及建议。
模型打包和框架
这决定了深度学习模型的执行方式,且推荐的选择可能取决于所使用的计算硬件,以及可移植性和运行时需求。以下是一些常见示例,并附有使用建议:
-
原始框架包装:你可以利用框架提供的特定优化和功能,从而提高性能。然而,在某些情况下,可能需要与特定硬件选项兼容,例如使用 TPU,而 TPU 仅支持 TensorFlow 框架。因此,如果你有 TPU 而选择使用 PyTorch,就无法使用 TPU。
-
开放神经网络交换 (ONNX) 框架:ONNX 提供了一种开放标准,用于表示深度学习模型,允许你将模型转换为不同的框架,并在各种硬件平台上运行。使用 ONNX 可以提高模型的灵活性和可移植性,使你能够从更广泛的硬件和基础设施选项中进行选择。此外,它还可以让你利用不同深度学习框架提供的优化和功能。一种方便且通用的解决方案是将模型转换为 ONNX 格式,之后可以根据需要轻松转换为其他格式。这种方法简化了过程,并确保与各种硬件和框架选项的顺利集成,例如在 iPhone 中利用 ANE 加速应用中的深度学习模型。
-
ONNX Runtime:这是一种推理加速器,旨在通过利用计算和内存优化来加速在任何硬件上的深度学习模型推理。在 ONNX Runtime 中运行模型比在本地深度学习框架(如 TensorFlow 或 PyTorch)中运行更快。
-
TensorRT:这是 NVIDIA 提供的高性能深度学习推理优化器和运行时/编译器库,旨在加速 NVIDIA GPU 上的深度学习模型推理。它支持 TensorFlow 和 ONNX,并提供便捷的方式将模型权重转换为与其框架兼容的格式,从而实现快速高效的模型部署。TensorRT 通过在较低层次调优模型,利用不同 GPU 内部硬件能力,提高 GPU 上的模型推理速度,最大化推理时的模型效率。由于 ONNX 权重与 TensorRT 兼容,将 PyTorch 模型权重转换为 TensorRT 兼容的权重格式的典型路径是先将 PyTorch 模型权重转换为 ONNX 权重。在 NVIDIA GPU 上,TensorRT 的推理速度被认为比 ONNX Runtime 更快。
-
开放视觉推理与神经网络优化 (OpenVINO):这是英特尔提供的一款工具包,旨在加速深度学习模型在英特尔硬件(包括 CPU、GPU 和 FPGA)上的推理。它支持 TensorFlow、ONNX 以及其他框架,提供在多种环境中的优化模型部署。
接下来,我们将探讨通信协议的选择和建议。
使用的通信协议
你应该使用的协议取决于运行时需求、网络负载需求、所选的用户界面、部署模式以及计算需求。以下是一些示例及其推荐:
-
MQTT:当你需要一个轻量级、低延迟的协议,适用于资源有限的设备(如物联网设备),并且实时通信和状态更新对你的应用至关重要时,可以使用 MQTT。功耗和散热是重要的考虑因素。
-
HTTP 或 REST API:当你需要一个受支持良好、易于实现的协议来处理网页服务和数据交换,且你的应用遵循请求-响应通信模式,并且需要符合数据保护法规和数据隐私要求时,选择此协议。
-
gRPC:当你需要高性能、低延迟协议来处理大规模分布式系统或微服务时,选择 gRPC,同时它还支持双向流和多种编程语言的支持。
-
服务器推送事件(SSE)或 WebSockets:当实时通知或实时更新对你的网页应用至关重要时,请使用它们。如果你需要服务器与客户端之间的单向通信,请使用 SSE。如果你需要服务器与客户端之间的双向通信,请使用 WebSockets。一个需要这些通信协议的显著领域是具有机器学习功能的实时协作工具。以下是一些示例:
-
Grammarly 使用 WebSockets
-
ChatGPT 使用 SSE
-
接下来,我们将探讨用户界面选择和推荐。
用户界面
在设计机器学习应用的用户界面时,必须考虑用户体验、可访问性、响应性和适应性等因素。以下是一些用户界面的建议:
-
网页应用程序:
-
它们适用于跨平台访问,因为用户可以通过网页浏览器访问应用程序
-
使用流行的网页开发框架,如 React、Angular 或 Vue.js 来构建响应式和互动性强的用户界面
-
示例用例:一个情感分析工具,允许用户输入文本并通过与机器学习模型的互动获得情感评分,界面基于网页
对于网页应用程序,你还需要根据提供的好处以及延迟权衡来明智地选择网页框架。请参考 https://www.techempower.com/benchmarks/?utm_source=pocket_mylist#section=data-r20&hw=ph&test=db 获取不同网页框架在单个网页 API 查询中的延迟估算。
-
-
移动应用程序:
-
非常适合通过智能手机和平板电脑随时访问机器学习功能
-
使用 Swift 或 Kotlin 开发 iOS 和 Android 平台的原生应用,或使用跨平台框架如 React Native 或 Flutter
-
示例用例:一个移动应用,利用机器学习模型进行图像识别,通过分析用户拍摄的照片来识别植物或动物
-
-
桌面应用程序:
-
适用于需要专用、平台特定应用并具备离线功能的用户
-
使用 Electron 或 Qt 等技术来构建跨平台桌面应用程序,或使用平台特定的语言,如 Windows 的 C# 或 macOS 的 Swift
-
示例用例:一个视频编辑软件,内置机器学习驱动的功能,如物体跟踪、自动色彩校正或场景检测
-
-
语音用户界面(VUI):
-
适用于通过语音命令与机器学习驱动的服务进行免提交互
-
集成流行的语音助手平台,如亚马逊 Alexa、谷歌助手或苹果 Siri
-
示例用例:一个语音激活的家庭自动化系统,使用自然语言处理根据用户命令控制智能设备
-
-
对话式 UI(聊天机器人):
-
适用于通过文本或语音对话更自然、互动地吸引用户
-
使用聊天机器人开发平台,如 Dialogflow、Rasa 或 Microsoft Bot Framework
-
示例用例:一个客户支持聊天机器人,使用机器学习驱动的自然语言理解来回答用户查询并提供帮助
-
-
增强现实 (AR) 和虚拟 现实 (VR):
-
适用于将现实世界和数字世界结合的沉浸式和互动式体验
-
使用 AR/VR 开发平台,如 Unity 或 Unreal Engine,并集成机器学习模型进行物体识别、运动跟踪或场景理解
-
示例用例:一个虚拟训练模拟器,使用深度学习模型实时分析和评估用户表现。在这个 AR/VR 应用中,用户可以练习各种技能,如医疗程序、机械修理或紧急响应场景。深度学习模型通过视觉输入评估用户的操作,提供即时反馈,并为改进提供个性化指导,增强学习体验并加速技能发展。
-
-
基于 API 的用户界面:基于 API 的用户界面提供了一种灵活且可扩展的方式,将你的机器学习模型与各种应用、平台和服务进行集成。这种方法允许开发者构建自定义用户界面或将机器学习驱动的功能整合到现有应用中,从而扩展模型的覆盖面和影响力。这适用于使其他应用、系统或服务能够以编程方式访问和与机器学习模型进行交互。以下是该方法的两个建议:
-
使用 REST、gRPC、SSE、Websockets 或 MQTT 创建结构良好且有文档的 API,将机器学习模型的功能暴露给外部客户端
-
实现身份验证和授权机制(例如,API 密钥和 OAuth)以确保对 API 的安全访问
-
示例用例:一个情感分析 API,允许开发者通过 API 调用发送文本数据并接收情感评分,将机器学习驱动的情感分析集成到他们的应用中
-
选择适合的部署选项涉及仔细评估架构选择、硬件选项、通信协议和用户界面,确保它们最能与特定需求和目标对接。通过考虑可扩展性、更新频率、集成需求和韧性等因素,你可以选择最合适的部署解决方案,从而最大化效率和投资回报率。
接下来,我们将讨论在部署深度学习模型时决策组件的一些实际示例。
基于实际用例探讨部署决策
在本节中,我们将探讨深度学习(DL)模型在生产环境中的实际部署决策,重点关注两个不同的使用案例:一个用于电子商务公司的情感分析应用和一个用于安全摄像头的面部检测与识别系统。通过分析这些真实世界的场景,我们将获得建立稳健部署策略的宝贵见解,以满足特定需求和目标。
探讨情感分析应用的部署决策
假设你正在开发一个情感分析应用,供电子商务公司用于实时分析客户评论。该系统需要每天处理大量评论,并且低延迟至关重要,以便为公司提供即时的洞察。在这种情况下,你的选择可能如下:
-
架构选择:作为独立服务部署,因为它可以提供更好的可扩展性,并且在处理不断增长的请求时更易于更新。
-
硬件/基础设施选择:云服务上的 GPU,因为它提供更好的并行处理能力和大规模并发请求的处理能力。
-
模型包装与框架:ONNX 和 TensorRT,因为它们提供高效的模型部署和推理加速。
-
安全性、信任与可靠性:实施数据漂移和模型性能的监控,定期用更新后的数据重新训练模型,并确保符合数据隐私法规。例如,对用户信息进行匿名化处理,并避免在分析中存储个人身份信息(PII),因为这可能会违反数据保护法规,如欧盟的 GDPR 或美国的 CCPA,具体取决于应用部署的国家。
-
通信协议:RESTful API 或 gRPC,因为它们非常适合用于 Web 服务,并能以低延迟处理大量请求。
-
用户界面:基于网页的仪表板,公司的员工可以在其中实时监控情感分析结果。
探讨安全摄像头的面部检测与识别系统的部署决策
假设你正在为需要实时检测入侵者的安全摄像头构建物体检测系统。在这种情况下,你的选择可能如下:
-
架构选择:本地边缘部署,因为它通过在数据源附近处理数据来提供低延迟和增强的安全性。此选择还减少了数据在网络中传输所需的时间,因为不需要将视频流传输到某个云服务器。
-
硬件/基础设施选择:根据与深度学习框架的兼容性和模型的复杂性,在边缘设备上选择 GPU 或 TPU。
-
模型包装和框架:ONNX 和 TensorRT,因为它们提供高效的模型部署和推理加速。
-
安全性、可信度和可靠性:实现对模型性能的监控,并确保遵守与视频监控相关的本地法规,如数据隐私、存储政策和同意要求,以维持视频分析的伦理和法律标准。例如,面部图像不应被存储,仅应存储提取的面部特征,因为这可能违反与个人数据保护相关的法规,具体取决于部署的国家。
-
通信协议:MQTT 或 WebSockets,因为它们提供边缘设备和中央监控系统之间的低延迟通信。
-
用户界面:一个桌面应用程序,显示带有物体检测叠加层的实时视频流,供安保人员监控。
通过考虑每个用例的具体需求,您可以就生产环境中部署深度学习模型所需的组件做出明智的决策。接下来,让我们进入一些成功深度学习模型部署的通用建议。
探索深度学习部署的通用建议
在这里,我们将发现与三大垂直领域相关的深度学习部署建议,即模型安全、可信度和可靠性保障,模型延迟优化,以及帮助抽象模型部署相关决策并简化模型部署过程的工具。我们将逐一深入探讨这三大领域。
模型安全、可信度和可靠性保障
确保模型安全、可信度和可靠性是部署深度学习系统的关键环节。在本节中,我们将探索各种建议和最佳实践,帮助您建立一个强大的框架,以维护模型的完整性。这包括遵守规定、实施安全防护措施、预测一致性、全面测试、分阶段和生产部署策略、可用性测试、重新训练和更新已部署的模型、人工干预决策和模型治理。通过采取这些措施,您可以有效地降低风险、提升性能,并增强用户对您深度学习部署的信任。
遵守规定并实施安全防护措施
法规遵从性和保障措施是负责任的深度学习部署的重要组成部分,确保你的模型遵守相关法律、行业标准和道德准则。实施强有力的合规框架不仅可以减少法律和声誉风险,还能增强用户和利益相关者的信任。这是一个非常广泛的话题,以下是一些可以借鉴的例子:
-
社交媒体平台内容审核:遵守社区准则和地区法律可以通过实施 AI 驱动的过滤器来检测和标记不当内容,设立人工审查流程处理模糊案件,并为用户提供透明的上诉机制。
-
AI 驱动的招聘工具:遵守反歧视法可以采取措施,例如监控偏见和公平性指标的表现,确保任何自动化决策对雇主和申请者都是透明且可解释的。
-
人脸识别系统:遵守隐私和道德准则可以通过以下步骤实现,例如在收集和处理个人生物识别数据之前获得明确同意,实施强大的数据安全措施,并确保关于系统能力和局限性的透明度。
-
基于深度学习的视频监控系统,例如人员检测:遵守隐私和道德准则可以通过措施实现,例如设置明确的标识牌,告知公众存在监控摄像头,限制数据访问仅限授权人员,并根据当地法规遵守数据保留和删除政策。
-
推荐系统(YouTube、Netflix 和 Tiktok):确保符合数据保护法规可以采取措施,例如实施隐私保护数据处理技术、为用户提供选择退出个性化推荐的功能,以及在数据收集和使用政策方面保持透明。
-
生成式 AI:合规性可以通过使用内容过滤机制来实现,以防止有害内容的生成,包括仇恨言论、淫秽材料和鼓励犯罪活动的内容,或者防止关于医疗问题的危险推荐。
随着我们继续探索模型安全性、信任和可靠性保障,让我们来审视确保深度学习部署中预测一致性的关键方面。
确保预测一致性
预测一致性是指模型在面对相同的输入数据时,能够生成相同的预测,无论硬件、序列化/反序列化过程、基础设施,还是单条数据还是随机批次。一致性差的预测可能导致对模型准确性和整体性能的预期偏差。为了在各种因素中保持一致性,必须跟踪并复制训练、评估和推理过程中涉及的环境依赖关系。像 Docker 这样的工具可以帮助创建具有特定依赖项的隔离环境,确保无缝体验并消除潜在问题。此外,考虑设置自动化测试,客观地防止任何不一致通过,基本上充当一个保护屏障。
接下来,我们将讨论全面测试在保持可靠的深度学习部署中的重要性。
测试
除了预测一致性测试,全面的测试通常可以确保你的深度学习模型和系统在任何时候都能按预期运行,并满足用户需求。深度学习系统本质上是软件系统,需要类似的措施来确保成功部署。测试组件如下:
-
单元、集成和功能测试:单元测试、集成测试和功能测试对确保软件组件的可靠性、可维护性和整体质量至关重要。以下是它们的重要性:
-
单元测试:侧重于个别组件或功能,验证其正确性并在开发早期隔离潜在问题。这有助于在问题蔓延前捕捉到错误,减少调试时间,提高代码可维护性。
-
集成测试:验证不同组件之间的交互,确保它们按预期协同工作。这有助于识别界面问题、数据流问题以及在组合组件时可能出现的不一致,确保顺利集成。
-
功能测试:评估软件是否能够实现其预定目的并满足用户需求。端到端功能测试确保软件在实际场景中正常运行,并提供良好的用户体验。
-
-
故障切换与恢复测试:验证模型在遇到硬件或软件崩溃等故障后,能否恢复并保持在意外中断情况下的高可用性。
-
负载压力测试:评估模型在不同负载条件下的表现,识别瓶颈并确保其能够处理预期的用户流量。这些测试还能帮助你发现错误,如 GPU 内存溢出、CPU 过载或存储不足等。
-
广泛且多样化的测试:模型可能无法优雅地处理意外的输入数据、边缘情况或系统故障,导致崩溃或不期望的行为。考虑所有可能的系统使用方式有助于你捕捉到系统中的问题。
-
采用分阶段部署和生产部署步骤:在深度学习(DL)生产部署中采用分阶段和生产部署策略,对于确保模型的可靠性和性能非常有益。这种方法包括为测试(阶段)和最终部署(生产)设置单独的环境,使你能够验证模型的行为并在模型上线之前识别潜在问题。通过采纳这一策略,可以最小化部署未经测试模型的风险,简化问题识别和解决的过程,并提高整个 DL 解决方案的可靠性。确保流水线能够在 24 小时内持续无故障地运行。
-
可用性测试:可用性测试的重点是确保软件应用程序能够提供高效、有效且令人满意的用户体验。自动化和手动测试互为补充,各有其独特的作用:
-
手动可用性测试:这涉及真实用户与软件的互动,以识别潜在的可用性问题、了解用户行为并收集定性反馈。手动测试有助于发现自动化测试可能无法检测到的问题,例如导航混乱、指令不清或主观偏好等。这种以人为中心的方法提供了关于用户如何感知软件的宝贵见解,并确定了改进的领域。
-
自动化可用性测试:自动化可用性测试通过使用工具和脚本来模拟用户互动、验证用户界面元素并检查可访问性和响应性,从而补充手动测试。自动化测试具有多个优势,包括提高效率、速度和覆盖面,以及能够在多个设备、平台和浏览器上进行一致的测试。这有助于识别在手动测试中可能不显现的可用性问题,从而确保一致且高质量的用户体验。
-
接下来,我们来讨论重新训练和更新已部署模型的重要性,以确保其持续的有效性和相关性。
重新训练和更新已部署的模型
重新训练和更新策略对于保持深度学习模型的有效性至关重要,因为它解决了随着数据模式变化可能需要定期更新的问题。通过定期在最新、相关的数据上重新训练模型,您可以确保它始终保持最新,并持续提供准确的预测。这不仅有助于保持模型的性能,还能让它与不断变化的趋势和用户需求保持同步。在第十六章《深度学习模型治理》中,我们将深入探讨重新训练和更新的重要性,探索其好处和最佳实践,帮助您在实际的深度学习部署中成功实施这一策略。
为了进一步提升我们的深度学习部署效果,我们将探索采用人类决策参与的决策流程的好处。
采用人类决策参与的决策流程
将人类决策参与场景纳入您的深度学习部署可以极大提高模型的性能和可靠性,无论是作为长期解决方案,还是在满足某些条件时触发警报。通过让人类专家参与决策过程,您可以弥合模型预测与现实世界复杂性之间的差距,从而做出更准确、细致的决策。这种合作方法通过利用人类的专业知识来验证、纠正和调整模型输出,推动持续改进。此外,人类决策参与系统还能够增强信任和责任感,因为用户可以放心地知道,复杂或高风险的决策不仅由算法做出,还得到了人类判断和监督的支持。
最后,我们将深入探讨模型治理在监督和管理整体深度学习部署过程中的关键作用。
模型治理
监控和治理在确保深度学习部署持续有效性和可靠性方面发挥着关键作用。通过跟踪模型的各个方面,如数据漂移和概念漂移监控,您可以识别并解决可能影响其长期表现的问题。数据漂移监控有助于检测底层数据分布的变化,而概念漂移监控则关注输入特征与目标变量之间关系的变化。建立一个强大的监控和治理框架使您能够主动管理模型的性能,并在面临不断变化的趋势和条件时保持其准确性。在第十六章《深度学习模型治理》中,我们将更详细地探讨这些方面,以及模型监控和治理的其他关键组件,帮助您制定一个全面的策略,保持深度学习部署的有效性。
接下来,我们将探讨模型延迟优化的建议。
优化模型延迟
假设你已经选择了理想的架构,训练了一个模型,提取了洞察,选择了推理模型编译器/加速框架,并为托管模型选择了目标硬件基础设施和架构,那么你可以采取额外的步骤来提高此阶段的模型延迟。可以采用以下技术:
-
模型剪枝:去除神经网络中不必要的神经元或权重,而不会显著影响整体性能。剪枝技术包括权重剪枝、神经元剪枝和滤波器剪枝。这可以减小模型大小和计算要求,从而提高推理速度。
-
模型量化:将模型参数(例如权重和偏置)的精度从 32 位浮动点数降低到较低位宽的表示方式,如 16 位或 8 位整数。量化可以加速模型推理,同时不会显著损失准确性,特别是在将深度学习模型部署到计算资源有限的硬件上时。
-
模型蒸馏:训练一个更小、更快的“学生”模型来模仿较大、较慢的“教师”模型的行为。学生模型从教师模型的输出中学习,以较低的复杂度和更快的推理速度实现相似的性能。此方法在第十三章中进行了展示,探索偏差和公平性。
-
模型并行:在模型并行中,神经网络的不同部分被分配到多个设备或处理器上,从而在不同的模型部分上进行并行计算。对于无法完全容纳在单个 GPU 内存中的巨大模型,这种方法是必不可少的。对于具有高度并行操作的模型,延迟可以显著减少。模型并行可以在各种并行级别上实现,例如层级并行、流水线级并行或张量切片级并行。
-
批量推理:通过批处理同时处理多个输入样本,使模型能够更好地利用底层硬件,从而提高整体推理速度。
接下来,我们将探讨一些抽象化部署的工具。
抽象化部署的工具
有许多工具和平台可用,帮助抽象化模型部署过程,使得在不同环境中部署机器学习模型变得更简单、更高效。以下是一些流行工具和平台的概述,包括开源工具和收费工具:
-
TensorFlow Serving(开源工具):一个灵活的高性能服务系统,用于在生产环境中部署 TensorFlow 模型,提供开箱即用的支持模型版本控制、REST 和 gRPC API,以及在 GPU 和 CPU 上的高效模型服务。
-
TorchServe(开源工具):PyTorch 的等效工具,类似于 TensorFlow Serving。
-
TensorFlow Extended (TFX)(开源工具):一个端到端的平台,用于在生产环境中部署、管理和维护机器学习管道。TFX 与 TensorFlow、TensorFlow Serving 及其他工具集成,提供无缝的部署体验。
-
MLflow(开源工具):一个开源平台,简化了端到端机器学习生命周期,包括实验、可重现性、部署和监控。它支持多种编程语言和机器学习库,是多样化项目的多功能选择。
-
Kubeflow(开源工具):一个与 Kubernetes 集成的解决方案,旨在促进可适应、可传输的机器学习任务的创建、协调、部署和执行。通过为不同的云服务提供商和本地基础设施提供一致的统一环境,它简化了部署过程。
-
Streamlit(开源工具):一个 Python 库,使开发者能够快速构建和部署机器学习与数据科学项目的定制化 web 应用。Streamlit 简化了创建交互式 web 应用的过程,几乎无需编码,从而更容易通过 web 应用共享和部署模型。
-
NVIDIA Triton(开源工具):一个开源工具,可用于部署深度学习(DL)模型。它原生支持许多框架,特别是 TensorRT、Pytorch、ONNX Runtime、OpenVINO,以及一个通用的 Python 后端,允许你包装和运行任何 DL 框架和 Python 代码。它通过 HTTP REST API 和 gRPC 协议提供预测。它还原生提供与 Prometheus 兼容的标准时间序列性能指标日志,之后可用于在 Grafana 仪表板中进行模型监控。它还允许我们在其 C API 中配置自定义指标。最重要的是,它简化了多个 GPU 的利用和 GPU 内存分配。我们将在下一节中实际探讨该工具的使用。
-
Azure ML 部署(付费工具):微软 Azure 的机器学习服务,简化了云端模型的部署。它提供了管理、监控和扩展已部署模型的工具,并支持如 TensorFlow 和 PyTorch 等流行框架。
-
DataRobot(付费工具):DataRobot 是一个自动化机器学习平台,简化了构建、部署和维护机器学习模型的过程。它提供了多种工具和功能,包括定制、模型版本控制、监控和协作。我们将在 第十八章 中探索该平台的使用,探索 DataRobot AI 平台。
-
Google Vertex AI(付费工具):谷歌云提供的托管机器学习平台,简化了端到端的机器学习工作流,包括模型训练、部署和管理。它与 TensorFlow、PyTorch 以及其他流行框架集成。
-
Amazon SageMaker(付费工具):AWS 提供的一项完全托管的机器学习服务,允许开发者快速轻松地构建、训练和部署机器学习模型。它支持多种框架,并提供模型版本管理、监控和扩展工具。
这些工具和平台有助于简化和优化模型部署过程,使开发者能够高效地在各种环境中部署他们的机器学习模型。
成功的 DL 部署需要解决关键方面,例如安全性、信任、可靠性和延迟优化,同时利用简化流程的工具和平台。通过遵循这些建议并使用合适的工具,开发者可以有效地部署和管理他们的 DL 模型,确保在各种环境中始终如一、可靠的性能。
到现在为止,你应该已经能明显看出,在选择 DL 部署系统组件之前,需要评估和考虑许多折衷和标准。然而,如果你没有付费工具,拥有一个 DL 模型,并且可以访问一个具有足够内存来托管模型的 GPU 机器,那么这三种工具就是不容置疑的选择。接下来的部分,我们将深入探讨这三种工具,并实际使用它们。
使用 ONNX、TensorRT 和 NVIDIA Triton Server 部署语言模型
这三种工具是 ONNX、TensorRT 和 NVIDIA Triton Server。ONNX 和 TensorRT 旨在执行基于 GPU 的推理加速,而 NVIDIA Triton Server 用于托管 HTTP 或 GRPC API。我们将在本节中实践探索这三种工具。TensorRT 被认为是最擅长优化模型以加速 GPU 推理的工具,而 NVIDIA Triton Server 是一款经过实践考验的托管深度学习(DP)模型的工具,并且与 TensorRT 原生兼容。另一方面,ONNX 是设置过程中的一个中间框架,我们主要用它来托管 TensorRT 直接支持的权重格式。
在本实践教程中,我们将部署一个来源于 Hugging Face 的语言模型,该模型可以在大多数 NVIDIA GPU 设备上运行。我们将把基于 PyTorch 的 Hugging Face 语言模型转换为 ONNX 权重,这将允许 TensorRT 加载 Hugging Face 语言模型。接下来,我们将创建代码和配置,要求 NVIDIA Triton Server 框架托管该语言模型。NVIDIA Triton Server 支持两种方式来部署模型,一种是将深度学习(DL)模型与其预处理和后处理方法作为一个单一管道部署,所有这些都嵌入在一个 Python 类中,另一种是通过逻辑上分离管道的不同组件来部署 DL 模型。图 15.1 描述了这两种方法,它们涉及需要两个模型的管道:

图 15.1 – 配置具有两个模型的管道中模型部署的两种方法
部署深度学习模型管道的一种直观且简单的方式是使用第一种方法。然而,第二种方法,通过将每个组件分开并拆分为独立的配置,提供了多种好处:
-
模块化和可重用性:模块化的特点使得各个部分可以在不同的管道或项目中重用。此外,它还允许在保持其他组件的同时,轻松地交换组件。
-
可扩展性和灵活性:这使得您可以将不同的组件部署到不同的 GPU,并为每个组件分配不同的实例进行同时运行。此外,这种方法允许与 CPU 绑定的方法不受 GPU 限制。
-
并行性和延迟减少:可以通过并行分支启用本地并行性,而不需要在 Python 代码中实现它。
将以下两种方法视为组织工厂流水线的方式:
-
在第一种方法中,整个组装过程被合并为一个单一的管道。这意味着所有组件都会按顺序处理并组合成一个集成过程。这种方法可能更容易设置和管理,但它的灵活性和可扩展性可能不如第二种方法。
-
在第二种方法中,组装过程被拆分为独立的模块化组件,这些组件可以单独管理和优化。这样可以提高灵活性和可扩展性,因为每个组件可以独立调整或更换,而不会影响整个管道。此外,这种方法支持并行处理,多个组件可以同时处理,可能会减少整体延迟并提高效率。
为了简化演示,我们将在这里展示第一种方法。
实际使用单一管道方法部署深度学习模型。
在本教程中,我们将探索使用 ONNX、TensorRT 和 NVIDIA Triton Server 部署深度学习模型的过程。在使用 NVIDIA Triton Server 部署模型时,您可能会遇到与模型加载、配置或推理相关的问题。以下是一些故障排除建议:
-
验证模型文件、配置文件和其他必需文件是否位于正确位置,并且具有正确的文件权限。
-
确保模型配置文件(
config.pbtxt)具有正确的设置,例如输入和输出张量名称、数据类型和维度。 -
检查 NVIDIA Triton Server 日志,查看是否有任何错误消息或警告,这些可能为问题提供线索。
-
确保必要的依赖项,如深度学习框架、ONNX 和 TensorRT,已安装并与您的系统和硬件兼容。
让我们以逐步的方式开始实际操作教程:
-
首先,我们需要通过运行以下代码来安装
transformer-deploy仓库:pip install git+ https://github.com/ELS-RD/transformer-deploy/tree/6b88e24ade6ce199e825adc0477b28a07f51f17d或者,我们可以使用以下命令:
git clone https://github.com/ELS-RD/transformer-deploy git checkout 6b88e24ade6ce199e825adc0477b28a07f51f17d transformer-deploy uses the tracing mode from PyTorch to convert a PyTorch model into ONNX. This involves sending some example PyTorch tensor data to the PyTorch helper tool, which will then trace the data shapes in the model through a forward pass to form the graph in ONNX. The following code shows a snippet of this tool as a pytorch library method:with torch.no_grad():
torch.onnx.export(
model_pytorch,
args=tuple(inputs_pytorch.values()),
f=output_path,
opset_version=13,
do_constant_folding=True,
input_names=input_names,
output_names=output_names,
dynamic_axes=dynamic_axis,
training=TrainingMode.EVAL,
)
这是我们使用 transformer-deploy 工具时发生的情况:
trt_engine = builder.build_serialized_network(network_def, config)
engine: ICudaEngine = runtime.deserialize_cuda_engine(trt_engine)
with open(engine_file_path, "wb") as f:
f.write(engine.serialize())
Here, `network_def` contains the ONNX graph definition of the model, and `config` specifies all the optimization strategies where the TensorRT default will be used. -
现在,我们将执行命令,将选定的 Hugging Face 文本生成模型转换为 TensorRT 序列化引擎:
roneneldan/TinyStories-3M. Additionally, one aspect that we need to take care of when using any graph optimizations and conversions to another framework is to make sure it maintains a satisfactory level of accuracy and doesn’t degrade too much from the model when it is run in the original base framework. A natural way to do this is to check the validation performance where the model is trained. In this case, we don’t know what dataset the Hugging Face model is trained from, so a workaround can be to take any relevant text generation dataset and validate on both the base framework and target framework setup. The transformer-deploy tool performs a simple predicted values deviation check to make sure the predicted values that are generated using the target framework don’t deviate too far from the values generated using the base framework. The atol parameter controls the leniency of this deviation check. -
在 步骤 4 中运行命令的结果如下:
[Pytorch (FP32)] mean=6.05ms, sd=0.21ms, min=5.91ms, max=8.12ms, median=6.01ms, 95p=6.27ms, 99p=7.16ms [Pytorch (FP16)] mean=7.76ms, sd=0.25ms, min=7.52ms, max=10.16ms, median=7.72ms, 95p=7.98ms, 99p=9.20ms [TensorRT (FP16)] mean=0.61ms, sd=0.06ms, min=0.56ms, max=0.86ms, median=0.57ms, 95p=0.70ms, 99p=0.71ms [ONNX Runtime (FP32)] mean=1.89ms, sd=0.06ms, min=1.81ms, max=2.48ms, median=1.88ms, 95p=1.93ms, 99p=2.21ms [ONNX Runtime (FP16)] mean=2.07ms, sd=0.09ms, min=1.99ms, max=3.11ms, median=2.06ms, 95p=2.11ms, 99p=2.62ms Each inference engine output is within 5.0 tolerance compared to Pytorch output triton_models/model.plan在这里,我们将把模型移到以下路径的新文件夹中:
models/transformer_tensorrt_text_generation/1/model.plan -
现在,是时候创建托管此 TensorRT 模型在 NVIDIA Triton Server 上所需的代码和配置了。按照 图 15**.1 中的方式 1,我们需要定义一个 Python 类,用于初始化并使用 Hugging Face 分词器和 TensorRT 引擎进行推理。让我们开始在这个部署 Python 代码文件中导入必要的 Python 库:
from typing import List import numpy as np import tensorrt as trt import torch import triton_python_backend_utils as pb_utils from transformers import AutoTokenizer, TensorType from transformer_deploy.backends.trt_utils import load_engine -
接下来,我们必须指定模型和我们存储序列化 TensorRT 模型的路径:
model = "roneneldan/TinyStories-3M" tensorrt_path = "/models/transformer_tensorrt_text_generation/1/model.plan" -
现在,我们必须定义
TritonPythonModel类接口,从初始化方法开始:class TritonPythonModel: def initialize(self, args): self.tokenizer = AutoTokenizer.from_pretrained(model) self.model_input_names = self.tokenizer.model_input_names trt_logger = trt.Logger(trt.Logger.VERBOSE) runtime = trt.Runtime(trt_logger) self.model = load_engine( runtime=runtime, engine_file_path=tensorrt_path )类名必须与初始化方法名相同,即
initialize。此代码从 Hugging Face 库方法中加载预训练的分词器,并从互联网下载分词器。需要注意的是,对于生产环境中部署的模型,建议将分词器权重或任何模型权重的管理实例存储在某个位置,以确保可靠的部署过程。此外,代码加载了序列化的 TensorRT 引擎。 -
接下来,我们需要定义分词器和模型的实际推理部分,如下所示:
def execute(self, requests): responses = [] for request in requests: query = [t.decode("UTF-8") for t in pb_utils.get_input_tensor_by_name(request, "TEXT").as_numpy().tolist()] tokens = self.tokenizer( text=query, return_tensors=TensorType.PYTORCH, return_attention_mask=False ) input_ids = tokens.input_ids.type(dtype=torch.int32) input_ids = input_ids.to("cuda") output_seq: torch.Tensor = self.model({"input_ids": input_ids})['output'].cpu().argmax(2) decoded_texts: List[str] = [self.tokenizer.decode(seq, skip_special_tokens=True) for seq in output_seq] tensor_output = [pb_utils.Tensor("OUTPUT_TEXT", np.array(t, dtype=object)) for t in decoded_texts] responses.append(pb_utils.InferenceResponse(tensor_output)) return responses -
这段代码应位于
models/transformer_tensorrt_text_generation/1/model.py路径下的 Python 代码model.py文件中。名为
1的文件夹是为了表示transformer_tensorrt_text_generation模型名称的版本。 -
我们需要的最终文件是一个配置文件,该文件指定了模型的名称、模型的最大批量大小、模型的后端类型(在本例中是 Python)、输入类型、模型的名称和维度、输出类型、模型的名称和维度、该管道的实例数量,以及是否使用 GPU 或 CPU。该文件需要命名为
config.pbtxt。我们使用的该文件内容如下:name: "transformer_tensorrt_text_generation" max_batch_size: 0 backend: "python" input [ { name: "TEXT" data_type: TYPE_STRING dims: [ -1 ] } ] output [ { name: "OUTPUT_TEXT" data_type: TYPE_STRING dims: [ -1 ] } ] instance_group [ { count: 1 kind: KIND_GPU } ]应该将其存储在以下文件路径下:
models/transformer_tensorrt_text_generation/config.pbtxt -
现在,我们有了运行 NVIDIA Triton Server 和部署我们的语言模型所需的所有代码和配置,这是一种基于
nvidia-docker的易于使用的部署,且有公开可用和可下载的镜像。可以通过执行以下命令将语言模型部署到 NVIDIA Triton Server:triton_client.py, which will define the code that’s needed to query the hosted model to obtain a generated text and print it out on the command line. The first step is to import the necessary libraries, which in this case will only be the HTTP client from the tritonclient library:导入 tritonclient.http 作为 httpclient
-
我们还需要指定在第 12 步中定义的
config.pbtxt文件中的模型名称以及模型版本,如下所示:MODEL_NAME = "transformer_tensorrt_text_generation" MODEL_VERSION = "1" -
现在,我们将使用
httpclient助手工具定义客户端,定义输入数据,配置输出数据,以便根据config.pbtxt指定的输出名称获取,并打印生成的文本:def main(): client = httpclient.InferenceServerClient(url="localhost:8000" input_text = np.array(["Tell me a joke."], dtype=object) input_tensors = [ httpclient.InferInput("TEXT", (1,), datatype="BYTES") ] input_tensors[0].set_data_from_numpy(input_text) outputs = [ httpclient.InferRequestedOutput("OUTPUT_TEXT") ] query_response = client.infer( model_name=MODEL_NAME, model_version=MODEL_VERSION, inputs=input_tensors, outputs=outputs ) output_text = query_response.as_numpy("OUTPUT_TEXT") print(output_text) if __name__ == '__main__': main() -
在命令行中运行
python triton_client.py将返回以下响应:b' you are big!"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n'
到此为止,我们完成了实践教程!
本主题旨在展示使用 NVIDIA Triton Server 加速部署语言模型所需的最小工作流,这与音频 DL 模型或计算机视觉 DL 模型并无太大区别。尝试使用这里展示的工作流与其他语言模型进行实验,并尝试调整所有设置!请注意,由于新语言模型中的高度自定义层,转换或优化阶段可能会遇到一些问题,因此你可能需要在基础库中修复这些问题,或者将其提交给相关团队并等待更新。
在专门使用语言模型进行部署时,还有一些额外的工具可以用于部署,它们由于对 Hugging Face 模型的高层次抽象以及对选定 LLM 的开箱即用官方支持,可能值得考虑。具体如下:
-
baichuan-inc/Baichuan-7B)、BLOOM(bigscience/bloom和bigscience/bloomz)、GPT-2(gpt2和gpt2-xl)、GPT BigCode(bigcode/starcoder和bigcode/gpt_bigcode-santacoder)、GPT-J(EleutherAI/gpt-j-6b和nomic-ai/gpt4all-j)、GPT-NeoX(EleutherAI/gpt-neox-20b、databricks/dolly-v2-12b和stabilityai/stablelm-tuned-alpha-7b)、LLaMA 和 LLaMA-2(meta-llama/Llama-2-70b-hf、lmsys/vicuna-13b-v1.3、young-geng/koala和openlm-research/open_llama_13b)、MPT(mosaicml/mpt-7b和mosaicml/mpt-30b)、OPT(facebook/opt-66b和facebook/opt-iml-max-30b) -
CTranslate2(https://github.com/OpenNMT/CTranslate2):这款工具以高效的推理性能为特点,支持以下模型:
-
编码器-解码器模型:Transformer 基础/大模型、M2M-100、NLLB、BART、mBART、Pegasus、T5 和 Whisper
-
仅解码器模型:GPT-2、GPT-J、GPT-NeoX、OPT、BLOOM、MPT、Llama、CodeGen、GPTBigCode 和 Falcon
-
仅编码器模型:BERT
-
-
文本生成接口(https://github.com/huggingface/text-generation-inference):如果没有加速器,这个工具效率较低,但它为以下模型提供了手动执行的离线优化:BLOOM、FLAN-T5、Galactica、GPT-Neox、Llama、OPT、SantaCoder、Starcoder、Falcon 7B、Falcon 40B、MPT 和 Llama V2
-
OpenLLM(
github.com/bentoml/OpenLLM):这款工具集成了 Langchain 和 Hugging Face 代理,但没有使用加速器/编译器库 -
Mlc-llm (
github.com/mlc-ai/mlc-llm):这支持多种设备,如手机
最后一点,如果我们没有遵循上一个主题中提出的建议和指南,这里呈现的实际部署示例将不会如此有效,因此请务必全面执行每一个建议!
摘要
在本章中,我们探讨了在生产环境中部署深度学习模型的各个方面,重点关注了关键组件、需求和策略。我们讨论了架构选择、硬件基础设施、模型打包、安全性、信任度、可靠性、安全性、认证、通信协议、用户界面、监控和日志记录组件,以及持续集成和部署。
本章还为根据特定需求选择正确部署选项提供了逐步指南,例如延迟、可用性、可扩展性、成本、模型硬件、数据隐私和安全需求。我们还探讨了确保模型安全性、信任度和可靠性的一般建议,优化模型延迟,并利用简化部署流程的工具。
使用 ONNX、TensorRT 和 NVIDIA Triton Server 部署语言模型的实际教程展示了加速部署所需的最小工作流程,展示了使用 NVIDIA Triton Server 的加速部署的实例。
通过理解并实施本章提出的策略和最佳实践,您可以成功地在生产环境中部署深度学习模型,并为每个所需组件的最明智选择发挥其全部潜力。要在成功的道路上进一步发展,我们需要确保在部署模型后不要忘记我们的模型,并始终考虑监控我们部署的模型。
在下一章中,我们将深入探讨我们需要考虑的监控许多方面,以确保我们机器学习用例的持续成功。
第十六章:深度学习模型治理
部署一个模型只是它旅程的开始。一旦它进入现实世界,它就像一个活生生的生命体——它需要高效使用以最大化效益,需要升级以保持锐利,需要维护以保持一致性表现,最终,还需要优雅地退出。想象一下路上的一辆车:你开始驾驶,但你也需要有效使用它、加油、保养,最终更换它或它的组件。深度学习模型的运作也同样如此。
模型治理作为引导力量,监督模型的使用,并始终保持对其性能和背景的警觉,确保模型能够持续、一致、可靠地传递价值。在深度学习领域,模型治理至关重要,它确保这些复杂的模型符合最高的质量、可靠性和公平性标准。
本章深入探讨了深度学习模型治理的三个基本支柱:引导模型利用的方向,着重于深度学习模型的合理应用;通过模型监控在各个方面保持对其性能的警觉;以及通过模型维护确保其在不断发展的深度学习领域保持最佳状态。通过实施健全的模型治理框架,深度学习架构师可以有效管理这些复杂模型所带来的挑战,并充分利用其巨大潜力,推动生产中的有价值的洞察和决策。本章将详细介绍这些模型治理的支柱。图 16.1 展示了我们将在本章探讨的模型治理概念的整体视图:

图 16.1 – 本章将介绍的概念背景下,模型治理的整体概述
具体来说,我们将涵盖以下主题:
-
深度学习模型利用的治理
-
通过监控治理深度学习模型
-
通过维护治理深度学习模型
技术要求
本章介绍了监控指标和设置警报的实际示例,利用前一教程中的代码,参考第十五章,在生产中部署深度学习模型。本教程要求你使用安装了 Python 3.10 和 nvidia-docker 工具的 Ubuntu 系统,并且拥有一台配有 NVIDIA GPU 的 Linux 机器。此外,我们还需要安装以下 Python 库:
-
numpy -
transformers==4.21.3 -
nvidia-tensorrt==8.4.1.5 -
torch==1.12.0 -
transformers-deploy -
Tritonclient
代码文件可以在 GitHub 上找到:github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_16。
深度学习模型利用的治理
模型利用,深度学习模型治理的第一支柱,对于这些复杂工具的负责任和道德部署至关重要。在本节中,我们将探讨模型利用的各个方面,包括护栏过滤器、问责制、合规性、验证、共享访问、透明度和决策支持系统。通过全面解决这些方面,深度学习架构师可以确保有效的模型利用,最大化模型的价值,同时减轻潜在的风险和意外后果。让我们深入了解这些方面:
-
护栏过滤器:这些过滤器在确保模型在既定边界内运行、最小化与不准确或有害预测相关的风险方面起着至关重要的作用。它们有助于保持模型的初衷。虽然使用模型预测的目标可以根据具体的使用案例大不相同,但一些常见类型的护栏在广泛应用中具有普适性:
- 防止有害使用每次预测:模型或其预测的有害使用可能涉及广泛的问题,包括与敏感属性相关的偏见、恶意攻击(如对抗性攻击)和与骚扰相关的文本生成。图 16.2展示了 OpenAI ChatGPT 在触发有害使用护栏后显示其预测的方式。

图 16.2 – OpenAI ChatGPT 的有害使用护栏触发响应
-
防止使用低置信度的预测:为了保持模型输出的可靠性,必须防止使用置信度低的预测。然而,问题在于回归模型的预测没有可以作为置信度得分的预测值。此外,尽管分类模型预测通常会应用 softmax 操作,使得预测值的和为 1,但它并没有适当地校准为实际的统计概率。一致性预测是一种经过实战考验的统计和强大的技术,能够为每个预测提供一个稳健的置信区间,从而更好地理解模型的确定性。此外,超出训练数据边界或发生漂移的输入数据可能会降低模型的性能,甚至在没有生成预测的情况下将其视为低置信度预测的特殊情况。
-
防止使用不准确的模型:通过持续监控和评估模型的准确性表现,可以确定何时停止使用某个模型,特别是在高风险的使用场景下,并进行模型维护,即重新训练和更新模型。
-
减少偏见:护栏过滤器有助于最小化与敏感属性(如种族、性别或民族)相关的偏见。通过防止模型产生可能导致歧视性结果的预测,护栏过滤器有助于更公平、公正地应用这些技术。
-
防止已知数据条件对模型性能产生负面影响:例如,面部识别系统应仅对正面无遮挡且没有戴口罩或眼镜的面部进行预测。在第十四章 分析对抗性表现中介绍的对抗性表现分析必须在部署前进行,以识别可能对模型性能产生负面影响的特征。在部署过程中,可以对已识别的特征施加适当的阈值,这些特征预计会恶化模型的表现,并作为预测预防的护栏。
-
仅对关键预测实施人工干预:在高风险场景中,例如医学药物推荐,涉及决策过程的人工专家是至关重要的,且在进行特定预测时,需要更高层次的专家参与。
-
问责制:这涉及到角色和责任的明确分配,并解决与模型所有权、合规性、训练数据以及每个开发阶段的审批流程相关的问题。问责制是人工智能和机器学习系统的关键方面,确保在整个模型生命周期中,角色、责任和所有权有明确的理解。它包括以下两个方面:
-
模型所有权:明确模型的所有者对于建立问责制至关重要。这包括确定负责模型开发、维护和更新的各方,以及那些因模型使用而可能产生不良后果的责任方。与模型所有权相关的其他关键考虑因素包括:
-
处理人员变动:当模型所有者离职或角色变动时,应该有一个明确定义的流程,将所有权和责任转交给另一位合适的个人或团队。这确保了模型能够继续得到适当的监督和维护,且问责制得以清晰保持。
-
共享访问和默认管理员角色:为了促进有效的模型治理并减少潜在的中断,建立共享访问和默认管理员角色至关重要。这允许多个团队成员监督模型的开发、维护和更新,从而减少对单个个体的依赖。此类共享访问应配备明确的角色和责任指南,以避免混乱并保持问责制。
-
-
处理开源模型:专有模型通常在单一组织内开发,这使得处理问责制变得简单,遵循前述的相关考量。而在开源模型中,开发过程通常涉及来自不同背景的多个贡献者,这使得建立问责制变得更加具有挑战性。为了解决这个问题,必须提供明确的贡献指南,保持模型开发历史的透明文档,并实施社区驱动的治理结构,或者指定一组核心维护者来监督该项目。作为替代方案,可以在组织中确立一个主要模型负责人,负责该组织中开源模型的所有使用责任。
-
预测责任:在高风险应用场景中,预测责任对于保持问责制和确保准确、可靠的结果至关重要。由于原始预测结果可能并不总是容易理解,因此通常需要后处理步骤将其转化为更易于理解的洞察或嵌套结果。在每个后处理阶段对结果的批准进一步确保了最终结果的质量和相关性,从而促进了 AI 和机器学习模型的负责任和有效使用。
-
-
模型与预测透明度:这对于促进对 AI 系统的信任和理解至关重要。这意味着要提供关于模型开发的清晰解释和相关信息,包括其架构、训练数据和方法论。提供这些洞察使用户能够理解模型如何生成预测,并确保 AI 系统与道德和负责任的实践一致,最终有助于更好的决策和更可靠的结果。这些解释可以是模型开发阶段用于理解和比较不同模型和预测的相同内容。
-
决策支持系统:这包括构建能够让决策者与模型预测和洞察互动的界面或平台。系统中应提供用户友好的仪表板、报告和可视化工具,同时将业务规则、法规和政策纳入决策过程中。这在高风险应用场景中再次具有重要作用。
现在,我们将深入探讨模型治理的第二个组成部分——监控已部署的模型。
通过监控来管理深度学习模型
模型监控对于维护深度学习模型在整个生命周期中的性能、可靠性和公平性至关重要。随着数据环境和业务需求的变化,持续监控可以帮助及早发现问题,如模型漂移、性能下降和潜在的偏差,从而确保持续交付准确且有价值的预测。这个过程包括收集和分析关键性能指标,持续评估模型输出与真实数据的对比,并识别任何可能影响模型效能的新兴趋势。通过实施一个强大的模型监控框架,深度学习架构师可以主动应对挑战,并做出有关模型更新、优化和再训练的明智决策,从而最大化模型的价值并减少与模型部署相关的风险。
模型监控只有在能够采取纠正措施来应对性能下降或不良情况时才具有价值。因此,监控的目标应该是识别和纠正不良行为。可以采取的措施更广泛地归入模型治理的第三支柱——模型维护,我们将在下一节单独讨论。现在,让我们深入探讨已部署机器学习模型的各种类别和具体指标,并举例说明哪些条件可能促使启动模型维护程序:
-
基于模型准确性的性能指标:这些是我们在第十章《探索模型评估方法》中更全面介绍的典型模型评估指标,如准确率、召回率、精确度、F1 分数、AUC-ROC 和对数损失。在模型开发和交付模型洞察阶段使用的相同评估指标应在此重用。当在未来能够获得真实标签时,这些指标可以通过两种方式进行监控:
-
自然情况:当使用场景是时间序列预测未来目标,或者目标对模型拥有者并非立即可得时,目标可以在未来自然获得。
-
手动标注:建议定期进行标注,选取一部分历史生产输入数据样本来验证模型性能的有效性。
导致模型维护触发的条件是使用在模型构建和评估实验过程中提到的相同使用场景有效性阈值。正如在第十章《探索模型评估方法》中强调的那样,这个阈值理想情况下应该与业务指标阈值有所关联。
-
-
数据质量度量:数据质量度量提供了关于输入数据的有效性、特征和一致性的基本见解。数据质量与模型的准确性和偏差表现相关,因此任何偏离常规的情况都可能导致准确性的下降。以下是一些此类度量的示例:
-
缺失或不完整数据计数:指数据集中数据缺失或不完全可用的实例数量。这会影响模型的准确性和可靠性,因为模型可能没有足够的信息来进行预测。
-
无效数据边界计数:指的是数据值超出可接受或预期范围的实例。这可能导致模型预测错误,因为模型可能从不正确的数据点中推断,而这些数据点并没有经过学习。
-
异常值和离群点指标度量:用于识别与数据集中的整体模式或趋势显著偏离的异常或极端数据点。这与无效数据边界有相同的根本原因。
-
数据漂移:当数据中输入特征的分布随时间变化时,就会发生数据漂移。这可能由于多种原因,例如数据源的变化、用户行为的变化,或外部因素影响数据生成过程。数据漂移可能导致模型性能下降,因为模型是在不同数据分布上训练的,可能无法很好地适应新的数据分布。监控数据漂移有助于识别何时需要重新训练或调整模型,以保持其准确性和有效性。在第十七章,在动态环境中有效管理漂移效应,我们将深入探讨用于检测数据漂移的技术,重点是深度学习特定数据输入。
-
概念漂移:指的是随着时间的推移,输入特征与目标变量之间关系的变化。这种变化可能导致一个原本准确的模型在性能上退化,因为模型所学的模式不再与不断变化的关系一致。这也与数据质量部分中介绍的标签一致性度量相关,详见第一章,深度学习生命周期。
-
-
系统性能度量:这些度量有助于确保部署的模型满足操作要求。系统性能度量下的主要子组如下:
-
推理延迟:指的是模型从输入数据生成预测或输出所需的时间。低延迟对于实时应用和用户体验至关重要,因为它确保模型提供快速及时的结果。
-
吞吐量:衡量模型在特定时间范围内能够生成的预测或输出的数量。高吞吐量对于处理大规模数据并保持所需的性能水平至关重要,尤其是在高需求场景中。
-
资源利用率:评估模型在运行过程中对资源(如 CPU、内存和存储)的使用效率。优化资源利用率可以确保模型在可用的基础设施上高效运行,降低成本并提升可扩展性。
-
排队延迟和请求数量:排队延迟指的是每个请求在被部署的深度学习模型处理前所经历的等待时间。监控排队延迟和请求数量有助于识别系统中的潜在瓶颈,并优化模型同时处理多个请求的能力。
-
-
警报和事件指标:这些指标有助于确保及时识别和解决问题,从而实现系统性能的最佳化。它们如下:
-
警报频率:此指标指的是在特定时间段内生成的警报数量,表示系统中的潜在问题或异常。监控警报频率有助于识别模式和趋势,从而采取主动措施防止或缓解重复出现的问题。
-
警报严重性:这衡量了一个问题对整体系统性能的影响程度。通过根据严重性对警报进行分类,可以优先处理最关键的问题,从而确保资源的高效利用,并最大程度地减少对系统的负面影响。
-
事件解决时间:这是处理和解决由警报引发的事件所花费的时间。跟踪这个指标有助于评估事件响应过程的有效性,并识别需要改进的地方,最终实现更快的解决时间和更好的系统性能。
-
-
模型公平性和偏差指标:在第十三章中介绍的用于比较不同开发模型的相同指标,也可以应用于监控已部署模型的公平性。
-
业务指标:监控与业务相关的指标对于评估部署的深度学习模型对组织目标的影响至关重要,并确保其与业务目标的对齐。并非所有东西都能通过数字来监控,因此要找出那些可以量化的组件。以下是一些需要考虑的指标:
-
关键绩效指标(KPIs):识别并跟踪受模型预测直接影响的关键绩效指标,例如收入、客户满意度、投资回报率或运营效率。这有助于评估模型对业务的整体贡献。
-
用户采纳与参与度:监控用户与模型的互动方式,包括使用模式、频率和反馈。这可以为模型的相关性、易用性及其在满足用户需求方面的整体效果提供洞察。
-
通过监控各种指标组的变化,不仅可以全面了解深度学习模型的性能、可靠性和公平性,还有助于识别新兴趋势和模式。通过密切监控这些指标,可以积极解决潜在问题,如模型漂移、性能下降和偏见,确保准确预测的持续交付。这也意味着分析监控指标中的模式对于制定改进计划以提升深度学习模型性能和解决潜在问题至关重要。
为有效分析和利用所监控的指标,建议将关键指标整合到一个全面的仪表板中,以便轻松跟踪和评估模型的整体健康状况,并最终增强监控过程。Grafana,一个流行的开源分析和监控平台,可以通过提供各种功能和集成有效地满足这些要求。随着我们的进展,我们将探讨如何通过使用 NVIDIA Triton 推理服务器、Prometheus 和 Grafana 来监控深度学习模型的实用教程。
使用 NVIDIA Triton Server、Prometheus 和 Grafana 监控部署的深度学习模型
NVIDIA Triton Server 通过 Prometheus 格式的 REST HTTP API 托管配置的指标,提供实时洞察,无需保留历史数据。要持续保存指标数据,需要配置 Prometheus 与 NVIDIA Triton Server 连接。虽然 Prometheus 可以随时间跟踪和记录指标,但它缺乏可视化能力。这就是 Grafana 的用武之地。它是一个平台,可以利用 Prometheus 记录的数据创建具有自定义图形和表格的动态仪表板。Prometheus 通过单独的 REST HTTP API 方便地共享其记录的信息,从而实现与 Grafana 的无缝连接。此外,Grafana 允许可靠地设置警报规则。
监控的第一步是计划我们想要监控的指标,我们将在接下来讨论。
选择要监控的指标
通过 NVIDIA Triton Server 托管的任何部署模型默认支持各种标准指标。这些指标包括以下内容:
-
推理请求指标:成功次数、失败次数、推理次数和执行次数
-
与 GPU 相关的指标:功率使用、功率限制、能量消耗、GPU 利用率、GPU 总内存和 GPU 已使用内存
-
与 CPU 相关的指标:CPU 利用率、CPU 总内存和 CPU 已使用内存
-
响应缓存指标:缓存命中次数、缓存未命中次数、缓存命中时间和缓存未命中时间
请注意,这些指标可以手动禁用。在这个实际示例中,我们将利用上一章中使用 NVIDIA Triton Server 部署的语言模型实现,并额外使用 Prometheus 和 Grafana 工具。NVIDIA Triton Server 记录的默认标准指标很有用,但我们还需要一些可能对业务有用且特定于语言模型的自定义指标。NVIDIA Triton Server 支持通过 C API 自定义指标,这意味着你需要开发 C 代码!然而,自从 NVIDIA Triton Server 版本 23.05 以来,有一种相对较新的方式支持自定义指标,那就是你可以使用 Python 为 NVIDIA Triton Server 定义自定义指标!我们将在我们的实际教程中探索这一新特性,以下是我们为语言模型探索的一些自定义指标,这些指标可能会有用:
-
处理的令牌数量:输入数据越大,请求的时间可能会越长。
-
生成的令牌数量:输出令牌数量越大,请求的时间可能会越长。
-
Flesch 阅读分数:这是一个阅读理解指标,用来衡量文本的易读性,它是一个有用的业务指标,因为生成的文本需要易于理解,才能发挥其作用。
现在我们准备好深入实践示例了。
跟踪和可视化所选指标随时间的变化
在开始之前,请确保你已经安装了 nvidia-docker、Prometheus、Node Exporter 和 Grafana 版本 v10.0.3。另外,确保 Prometheus 和 Grafana 可以从命令行的任何位置调用。我们将按照以下步骤逐步开始这个过程:
注意
我们正在利用来自第十五章的代码,将深度学习模型部署到生产环境。这里需要的第一个更改是,我们将在model.py文件中的TritonPythonModel基础上进行修改。NVIDIA 提供的 Python 自定义指标 API 允许你在TritonPythonModel中定义和记录指标,直接在你可以定义的三个方法中进行:initialize、execute和finalize。
-
首先,我们将使用的附加库是
textstat和nltk,这些将用于计算可读性分数:import textstat import nltk nltk.download('punkt') -
第一步是在
initialize方法中初始化指标日志记录实例。Prometheus 支持四种指标类型:计数器(用于增加的值)、仪表(用于波动的值)、直方图(用于观察值的分布)和摘要(用于跟踪数据中的分位数)。我们计划添加的三个自定义指标本质上是波动的值,任何直方图都可以在 Grafana 中创建。让我们定义我们将使用的指标系列。你可以为每个指标系列设置名称、描述和类型。此外,你可以为任何逻辑组创建多个指标系列。就我们的情况而言,所有三个我们计划的指标都是业务指标,且是波动的值:self.metric_family = pb_utils.MetricFamily( name="business_metrics", description="", kind=pb_utils.MetricFamily.GAUGE ) -
现在,让我们在这个指标系列中定义指标:
self.number_of_input_tokens_metric = self.metric_family.Metric( labels={"name": "Number of input tokens","version": "1"} ) self.number_of_tokens_generated_metric = self.metric_family.Metric( labels={"name": "Number of tokens generated", "version": "1"} ) self.readability_metric = self.metric_family.Metric( labels={"name": "Flesch Readability Score", "version": "1"} )方便的是,如果任何逻辑需要更改,您可以对指标进行版本控制,这使得监控过程更为稳健。
-
接下来,我们将在每次执行时记录指标。我们将定义一个辅助方法,随后将在
execute方法结束时执行:def _compute_custom_metrics(self, input_tokens, generated_tokens, generated_text): self.readability_metric.set(textstat.flesch_reading_ease((generated_text))) self.number_of_input_tokens_metric.set(input_tokens.shape[1]) self.number_of_tokens_generated_metric.set(generated_tokens.shape[1]) -
最后,我们将在
execute方法下使用这个辅助方法:self._compute_custom_metrics(input_ids, output_seq, " ".join(decoded_texts)) -
现在,我们需要使用相同的命令启动 NVIDIA Triton Server
nvidia-docker实例,命令如下:sudo docker run --gpus=all -it --shm-size=256m --rm -p8000:8000 -p8001:8001 -p8002:8002 -v ${PWD}/models:/models nvcr.io/nvidia/tritonserver:23.05-py3 -
之后,我们进入 Docker 环境,接下来的步骤是安装必要的库:
pip install transformers==4.21.3 nvidia-tensorrt==8.4.1.5 git+https://github.com/ELS-RD/transformer-deploy torch==1.12.0 -f https://download.pytorch.org/whl/cu116/torch_stable.html textstat==0.7.3
注意
对于生产环境,请务必创建一个 Docker 镜像,其中所有库都已经固定,这样您就不再需要手动安装库。
-
现在,您可以在命令行中执行
python triton_client.py命令并获取预测结果。 -
默认指标和自定义指标立即托管在 URL
http://localhost:8002/metrics中,您可以查看实时的文本格式指标。如果您使用的是远程服务器,可以将localhost替换为服务器的 IP 地址。以下代码片段展示了可以在前述 URL 中找到的实时 Prometheus 格式的指标:# HELP nv_gpu_power_usage GPU power usage in watts # TYPE nv_gpu_power_usage gauge nv_gpu_power_usage{gpu_uuid="GPU-246b298f-13e6-f93e-b6f1-0fb8933ce337"} 13.676 nv_gpu_power_usage{gpu_uuid="GPU-0fa69700-a702-0113-f30c-89d3fa1cec2f"} 19.626 # HELP nv_gpu_power_limit GPU power management limit in watts # TYPE nv_gpu_power_limit gauge nv_gpu_power_limit{gpu_uuid="GPU-246b298f-13e6-f93e-b6f1-0fb8933ce337"} 250 nv_gpu_power_limit{gpu_uuid="GPU-0fa69700-a702-0113-f30c-89d3fa1cec2f"} 260 # HELP nv_cpu_utilization CPU utilization rate [0.0 - 1.0] # TYPE nv_cpu_utilization gauge nv_cpu_utilization 0.04902789518174133 # HELP nv_cpu_memory_total_bytes CPU total memory (RAM), in bytes # TYPE nv_cpu_memory_total_bytes gauge nv_cpu_memory_total_bytes 67239776256 # HELP nv_cpu_memory_used_bytes CPU used memory (RAM), in bytes # TYPE nv_cpu_memory_used_bytes gauge nv_cpu_memory_used_bytes 14459383808 # TYPE business_metrics gauge business_metrics{name="Flesch Readability Score",version="1"} 93.81 business_metrics{name="Number of tokens generated",version="1"} 128 business_metrics{name="Number of input tokens",version="1"} 11 -
由于这些只是实时指标,我们需要设置本地服务器或使用在线 Prometheus 服务器。在此步骤中,我们将选择本地托管的 Prometheus 服务器,需要在命令行中运行以下命令:
sudo systemctl start node_exporter node_exporter here is a Prometheus exporter that collects system-level metrics from target machines, enabling Prometheus to monitor and analyze their resource usage and performance. -
现在,我们需要将 NVIDIA Triton Server 端点添加到 Prometheus 配置文件中,以便跟踪指标。为此,请在命令行中执行
sudo gedit /etc/prometheus/prometheus.yml,然后添加以下作业详情:- job_name: "triton" scrape_interval: 10s static_configs: - targets: ["localhost:8002"]这样,Prometheus 就可以从 NVIDIA Triton Server 记录指标了。
-
Prometheus 默认使用端口
9090托管其 Web 应用。因此,通过浏览器访问链接localhost:9090将进入 Prometheus 首页。进入 状态 标签,点击下拉菜单中的 目标,将显示以下截图,验证 Triton 端点正在被跟踪。

图 16.3 – 左侧是 Prometheus Web 应用主页,右侧是 Prometheus 正在跟踪并从中获取指标的目标
注意
默认情况下,Prometheus 不强制用户账户管理,但可以配置为强制执行。
-
接下来,我们将设置 Grafana 以连接到本地托管的 Prometheus 实例。首先,我们需要通过在命令行中执行以下命令来启动 Grafana 服务:
3000, so accessing the localhost:3000 link in a web browser will bring you to the home page. The default username and password are admin and admin. Once that is filled in, click on Log In and create a new password, after which you will be greeted with Grafana’s home page. We can set up the Prometheus link now by clicking on the three-line button on the top-left tab and clicking on the Data sources tab under the Administration dropdown, as shown in *Figure 16**.4 (a)*. This will result in the screen shown in *Figure* *16**.4 (b)*.

图 16.4 – 显示如何在 Grafana Web 应用中导航到添加数据源页面的截图
- 接下来,点击 Prometheus 作为数据源,您将看到如图 16.5 (a)所示的屏幕。将 Prometheus 默认托管的 Web 应用链接设置为
http://localhost:9090,然后点击保存并测试。这应该会显示如图 16.5 (b)所示的成功界面。

图 16.5 – Grafana Prometheus 数据源设置标签页(a)和成功创建的屏幕(b)
- 这样,您将看到数据源已成功创建,如图 16.6 (a)所示。此时,我们可以创建一个仪表盘来可视化我们正在监控的指标。Grafana 允许通过三种方式创建仪表盘:通过其公开共享的仪表盘 ID 导入,通过导出的仪表盘 JSON 文件导入,以及创建新的仪表盘。在 Grafana 中,您可以使用内置的可视化 UI 构建器系统或基于PromQL的可视化手动创建多种类型的可视化,并选择它们的展示方式。然而,在本教程中,我们将通过导入一个已有的仪表盘 JSON 文件来使用现成的可视化。为此,使用图 16.4 (a)所示的三行按钮下拉菜单导航到仪表盘页面。进入仪表盘页面后,点击新建,然后点击导入,如图 16.6 (b)所示。

图 16.6 – Grafana 数据源标签页显示已创建的数据源,仪表盘标签页显示新建按钮的下拉菜单
- 将提供的
Triton Inference Server-1692252636911.json文件直接拖入导入区域,然后连接到您创建的 Prometheus 数据库,您将看到如图 16.7所示的仪表盘。

图 16.7 – 用于监控教程的自定义 Grafana 仪表盘
请注意,生成该指标的机器上有两块 GPU,这也是为什么会有两个 GPU 统计数据。此可视化有效地展示了大多数默认的 NVIDIA Triton Server 指标,并通过图形显示了这三个额外的自定义指标,这些图形捕捉了它们的历史值直到当前时刻。然而,硬件资源特定的统计数据是一个例外,它们仅以实时方式展示。
现在,监控中缺少的部分是创建规则和条件,这些规则和条件被认为是警报事件,称为事件警报组件。没有警报的深度学习模型监控就像有了安防摄像头,却没有警报系统。你不会知道问题何时发生,直到为时已晚。事件可能包括模型准确度下降、响应延迟、资源瓶颈、在监控已部署深度学习模型时的意外输出变化和硬件故障等。Grafana 具有内建的警报管理、通知管理和联系人管理系统,我们将在接下来的部分中利用这些功能。
使用 Grafana 设置警报
让我们来看看如何使用 Grafana 设置警报的步骤:
- 点击警报标签,如图 16**.4 (a)所示,然后点击警报规则。你将看到如图 16**.8所示的界面。

图 16.8 – NVIDIA Triton 请求失败警报规则的警报规则标签设置
-
在这个例子中,我们将设置一个警报,当有任何 NVIDIA Triton Server 推理请求失败时触发。所以,在同一标签页中,选择
nv_inference_request_failure指标标签,并将阈值设置为小于 1 的数值,这样单个失败请求就会触发警报。如图 16**.8所示,数值被设置为0.8。 -
接下来,将评估间隔设置为一分钟,并且仅在连续五分钟内出现请求失败时才会触发警报,如图 16**.9所示。然后,点击保存并退出按钮。

图 16.9 – 警报规则的评估间隔设置
Grafana 中的警报可能有三种状态:正常,表示条件未触发;待处理,表示条件部分触发,但行为还不一致;触发,表示条件已持续满足,并且警报已被触发。现在,警报规则已保存并创建,你将看到如图 16**.10 (a)所示的界面,状态为正常。图 16**.10 (b)展示了待处理阶段,其中检测到失败,但还不够一致,无法触发警报。另一方面,图 16**.10 (c)展示了触发阶段,其中故障已按照配置的时间间隔持续发生。

图 16.10 – 正常状态(a)、待处理状态(b)和触发状态(c)的警报状态
- 要配置这些警报发送到哪里以及发送给谁,我们将使用联系人和通知策略选项卡,这些都在警报部分。首先,点击联系人选项卡,设置将接收通知的人员。你甚至可以将这些人员组织成小组,但为了简化本教程,我们将通知发送给自己。Grafana 提供了多种联系方式集成:Alertmanager、Cisco Webex Teams、钉钉、Discord、电子邮件、Google Chat、Kafka REST 策略、LINE、Microsoft Teams、Opsgenie、PagerDuty、Pushover、Sensu Go、Slack、Telegram、Threema Gateway、VictorOps、Webhook 和 WeCom。为了简化操作,我们将选择一个广泛可用的集成类型:电子邮件。Grafana 使用sSMTP软件发送电子邮件,因此在继续操作之前,请确保你已设置了电子邮件账户并配置了凭据。在联系人设置中,输入你的姓名和电子邮件,然后点击测试以生成一个测试通知,确认凭据准确。确认收到电子邮件通知后,保存设置。有关设置界面的示例,请参见图 16**.11。

图 16.11 – 设置电子邮件的联系人选项卡
- 接下来,我们需要将联系人与默认通知策略关联起来。转到通知策略选项卡,点击设置,并将默认联系点更改为我们在第 4 步中设置的电子邮件联系人。然后你将看到一个类似于图 16**.12的界面。

图 16.12 – 设置为通知我们的默认通知策略
- 现在,我们已经设置好接收电子邮件通知!作为一个挑战,尝试找出一些方法让推理服务器请求失败,如果无法做到,修改规则为某个一定会触发的条件,这样你就能通过电子邮件收到一个实际的警报通知示例。图 16**.13展示了你将通过手机界面收到的示例电子邮件。

图 16.13 – 示例触发的警报电子邮件通知,状态为 Firing
通过这些,我们成功为部署的深度学习模型设置了监控和警报系统!这个实现中的一个显著注意事项是,指标与模型执行捆绑在一起。为了解耦以避免增加特定推理预测的运行时,解决方案是使用 C API 来构建自定义指标。如果记录指标所需的时间不重要,你也可以考虑在 NVIDIA Triton Server 中托管另一个“模型”,该模型接收来自特定推理模型的输出并记录指标。NVIDIA Triton Server 还提供了一个名为perf_client的工具,可以评估不同配置的运行时,帮助你优化系统的性能。具体来说,该工具衡量并报告在不同负载条件下的吞吐量和延迟。
然而,仅仅拥有监控和警报并不能提供完整的模型监控视图。我们需要深入分析这些数据,交叉参考它们,发现连接并找出模式。这就像检查汽车的燃油效率、轮胎压力和发动机温度,确保驾驶顺畅。
此外,仅靠警报无法解决问题。它们就像汽车的警示灯——它们告诉你出现了问题,但你仍然需要停车,打开引擎盖并修复问题。这就是模型维护的作用。在下一部分,我们将探讨如何不仅检测问题,还能采取行动,确保模型在长期内保持平稳高效地运行。
通过维护管理深度学习模型
指标记录、仪表盘构建、记录的指标分析和警报是模型监控的核心组成部分,但它们只有在采取适当的行动之后才有效,这些行动包含在模型维护中。模型维护类似于赛车中的技术维修团队,定期对深度学习模型进行微调和优化,以确保它们高效且有效地运行。就像技术维修团队快速修理、加油和调整赛车部件以适应变化的赛道条件一样,模型维护涉及到更新模型以应对环境变化,利用反馈回路获取的新数据改进和完善模型,并针对杂项问题进行事件响应。这确保了模型始终保持在正确轨道上,提供有价值的见解,并在数据和业务需求不断变化的环境中推动明智的决策。
模型维护的关键方面全面包括以下内容:
-
建立反馈回路:建立反馈回路对于捕获实际结果和验证模型预测至关重要,它使深度学习从业者能够识别改进的领域,并相应地调整模型。
-
重新训练:重新训练是模型维护的一个关键环节,确保模型能够跟上最新的数据和趋势,从而保持准确性和相关性。定期重新训练可以使模型从新的见解中学习,并适应不断变化的数据环境,保证稳定的性能。幸运的是,对于深度学习模型,可以使用微调过程,这比完全重新训练过程要快得多。以下是两个强调频繁更新和模型重新训练重要性的使用案例:
-
电子商务产品推荐:在电子商务中,消费者偏好和产品可用性变化迅速。为了提供相关的产品推荐,深度学习模型需要频繁地进行重新训练,可能是每周甚至每天一次,以理解最新的趋势和客户行为。
-
社交媒体情感分析:社交媒体平台不断发展,出现新的趋势、标签和用户行为。为了准确评估公众情感和意见,深度学习模型需要经常重新训练,可能是每季度一次,以适应这些变化。
-
-
事件响应处理:当警报信号显示潜在问题时,拥有一个专门的响应团队来及时处理和解决问题至关重要。该团队应该具备充分的能力,调查根本原因,采取纠正措施,并防止类似问题在未来再次发生。让我们来看看不同类型事件的响应处理建议:
-
数据相关事件:这些事件发生在模型接收到不正确、不完整或有偏的数据输入时。处理此类问题时,响应团队应该与数据提供者紧密合作,识别原因,纠正数据,并在需要时重新训练模型。
-
模型性能事件:这些事件涉及模型生成不准确或出乎意料的预测。正确的处理方式需要模型拥有者(负责模型创建或批准模型使用)与预测拥有者(负责批准预测使用)之间的合作,正如前面深度学习模型利用治理部分所描述的那样。两者应该分析模型的表现,识别架构或训练中潜在的问题,并实施改进,确保未来能够提供更好的性能。
-
基础设施相关事件:这些事件由硬件或软件故障引起,影响模型的部署环境。响应团队应该与基础设施提供商或团队合作,解决问题,确保模型能够顺利运行。
-
安全事件:这些事件涉及未经授权的访问、数据泄露或其他恶意活动,目标是模型。响应团队应该遵循组织的安全政策,识别威胁,并采取适当的措施来减轻风险。
-
合规性和监管事件:当模型的输出或操作违反了法律或监管要求时,就会发生这些事件。响应团队应与法律和合规团队合作,处理违规问题并修改模型以符合必要的规定。
-
通过全面考虑此处分享的模型维护组件,组织可以有效解决已部署深度学习模型所面临的挑战,确保其持续改进并与业务需求对齐。传统上,这些维护操作是在警报触发后手动执行的。然而,也可以根据警报事件安排定制任务以自动执行。考虑使用 Apache Airflow 来协调从模型监控警报中触发的自动化任务。Apache Airflow 就像是数据任务的指挥,允许你在有向无环图格式中编排和调度复杂的工作流。它让你定义、自动化并监控任务序列,确保它们按正确的顺序和时间发生。然而,基于模型监控警报创建自动化任务存在一些固有的局限性和风险,接下来我们将简要探讨这些问题。
探讨使用由模型监控警报触发的自动化任务的局限性和风险
尽管基于模型监控警报自动化任务可以节省时间和资源,但在实施这种方法时也存在局限性和潜在风险,必须加以考虑。基于模型监控警报自动化任务的一些局限性如下:
-
问题复杂性:有些问题可能过于复杂或微妙,无法通过自动化过程有效处理。例如,在一个医学影像分析的深度学习模型中,当监控警报表明准确率下降时,可能触发一个自动化任务来重新训练模型。然而,问题的复杂性可能源于训练数据的不平衡,比如某种疾病的代表性不足,而仅仅通过重新训练模型是无法解决的。在这种情况下,自动化过程可能无法有效解决问题的根本原因。
-
缺乏上下文:自动化任务可能缺乏考虑问题更广泛背景的能力,也无法理解其对系统其他方面可能产生的影响。
假设有一个深度学习模型,根据各种行为和人口统计因素预测客户流失。可以设置一个自动化任务,当检测到高流失风险的客户时,向他们发送促销优惠。然而,该任务可能没有足够的上下文来考虑外部因素,比如近期的负面宣传事件或广泛的服务中断,这些因素可能导致流失风险的暂时性增加。这种缺乏上下文的情况可能导致不必要的促销优惠和资源的无效使用。
-
不充分或不适当的响应:自动化任务可能无法始终选择最合适的响应措施来应对警报,这可能导致次优的结果。例如,一个监控社交媒体帖子中有害内容的 AI 模型可能会检测到包含冒犯性语言的帖子。自动响应系统可能会立即删除帖子或禁用用户,而没有考虑到误报的可能性或帖子的更广泛背景(例如,引用冒犯性语言以进行批评)。
至于启用基于模型监控警报的自动化任务所涉及的风险,如下所示:
-
过度依赖自动化:过度依赖自动化任务可能导致模型维护过程中缺乏人工监督和专业知识。这可能导致忽略只有人类直觉才能察觉的微妙模式和趋势,进而可能导致模型性能不佳。
-
不准确或过早的触发:自动化任务通常是根据监控指标中的特定条件触发的。如果这些条件没有被仔细定义,任务可能会不准确或过早地触发,导致对模型采取不必要甚至有害的行动。
-
缺乏灵活性:自动化任务通常是为特定场景或问题设计的,可能不足以应对无法预见或复杂的情况。这可能限制其在处理模型维护过程中出现的独特挑战时的有效性。
-
错误叠加的风险:当自动化任务根据错误的警报或不准确的指标执行时,它们可能通过对模型进行不必要或不正确的调整来加剧问题。这可能导致模型性能进一步恶化,甚至造成不可逆的损害。
-
安全风险:基于警报自动化任务可能会使模型及其基础设施面临潜在的安全风险,特别是如果自动化系统没有得到充分的安全保障。未经授权的访问或操作自动化系统可能会导致意外后果或恶意行为影响模型。
为了缓解这些局限性和风险,必须在自动化和人工参与之间找到平衡。这可以通过引入人类介入系统、确保监控指标和警报的适当验证与校准,并实施强有力的安全措施来保护自动化基础设施来实现。
通过这一点,我们已经涵盖了深度学习模型治理的所有组成部分。这种三支柱的整体方法最终使组织能够持续并一致地发挥深度学习模型的全部潜力,在现实世界中推动有价值的洞察力和信息驱动的决策。
摘要
在本章中,我们探讨了深度学习模型治理的三大基础支柱:模型利用、模型监控和模型维护。模型利用确保深度学习模型的有效、效率、伦理和负责任的使用,而模型监控则允许对性能进行持续评估,识别潜在的偏差或漂移,并监控基础设施相关的指标。另一方面,模型维护侧重于定期更新和完善,以确保模型与不断变化的数据环境和业务需求保持一致。
我们还深入了解了使用 NVIDIA Triton Server、Prometheus 和 Grafana 监控深度学习模型的技术步骤。通过认真考虑模型治理的各个组成部分,深度学习架构师可以有效管理这些复杂模型在生产环境中的挑战,并持续挖掘它们为推动有价值的洞察和决策所带来的潜力。
在下一章中,我们将进一步深入探讨深度学习模型漂移检测的细节。
第十七章:在动态环境中有效管理漂移
漂移是已部署深度学习模型随着时间推移性能下降的一个重要因素,涵盖了概念漂移、数据漂移和模型漂移。让我们通过一个基于烹饪的类比来理解已部署模型的漂移。假设一个已部署的深度学习模型就像一位技艺精湛的厨师,他旨在创造让顾客满意的菜肴,但他擅长某种特定的菜系。概念漂移发生在顾客的口味偏好发生变化时,这会改变食材和受欢迎菜肴之间的关系,从而影响顾客的口味需求。而数据漂移则是当食材本身发生变化时,比如口味的变化或食材的供应变化。最后,模型指标监控警报最直接的表现是当厨师失去顾客时。在所有这些情况下,厨师都必须调整菜肴以维持其成功,就像深度学习模型需要更新,以应对概念漂移(即输入和目标变量之间关系的变化)以及数据漂移(即输入数据分布和特征的调整)。
监测漂移对于确保深度学习模型的持续成功至关重要,就像厨师需要跟踪顾客不断变化的口味和食材的变化一样。作为一个预告,只有某些用例需要监控。在本章中,我们将深入探讨测量和检测漂移的技术,这将使我们能够有效监控并在检测到漂移时及时发送警报,并进行必要的模型维护调整。通过与厨师类比,监控漂移的技术可以类比为厨师观察顾客反应、阅读评论或收集反馈,以更好地了解顾客的偏好和食材的质量。通过保持警觉,避免任何漂移,厨师和深度学习模型都能适应和进化,保持专业水平,并在动态环境中提供卓越的成果。具体来说,我们将涵盖以下主题:
-
探索漂移问题
-
探索漂移的类型
-
探索处理漂移的策略
-
以编程方式检测漂移
-
比较和对比 Evidently 和 Alibi-Detect 库在漂移检测中的应用
技术要求
本章将涵盖一个实际示例,用于测试数据漂移技术。我们将使用 Python 3.10,此外,我们还需要安装以下 Python 库:
-
evidently -
numpy -
transformers==4.21.3 -
torch==1.12.0 -
syllables -
audiomentations -
datasets
代码文件可以在 GitHub 上找到:github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_17。
探索漂移问题
漂移最明显的问题是准确度的下降。然而,漂移带来的问题比你最初注意到的要多,包括以下几点:
-
适用性:随着数据模式和分布的变化,模型对新数据的准确预测能力可能会受到影响。这可能导致模型在现实场景中的效果降低,从而减少决策价值,进而增加模型变得不相关且难以使用的可能性。
-
可解释性:理解和解释模型的决策可能变得具有挑战性,因为影响其预测的因素可能不再与当前的数据环境相符。这可能会妨碍与利益相关者的有效沟通,并且削弱对模型预测的信任。需要注意的是,原本可以解释的模型仍然是可以解释的,因为我们仍然可以准确地说明它是如何使用输入数据的,但随着数据的漂移,解释可能变得更加困难。
-
公平性:偏见和差异可能会出现或加剧,引发模型输出中的公平性问题。这可能导致不同群体受到不平等待遇,延续有害的不平等,并在模型应用中带来伦理问题。
-
稳定性:对输入数据变化的敏感性可能导致性能波动,影响模型的稳定性和一致性。不稳定的模型可能导致不可靠的结果,使决策者难以依赖模型的输出。
这些问题全面地突显了数据漂移可能带来的挑战。现在,我们已经了解了三种高层次的漂移类型。这些是有用的,但不足以实施漂移检测。了解可能影响模型的漂移类型将帮助你为任何部署相关的问题做好准备,并实施漂移监测,这也引出了下一个话题。
探索漂移的类型
漂移就像是数据工作方式的变化。当数据本身发生变化,或其来源的环境发生变化时,就会发生漂移。这种变化有时会突然或快速发生,有时会很缓慢,甚至以某种重复的模式出现。面对漂移时,重要的是要看到大局,而不仅仅是几次偶然的异常波动。漂移并不是指那些稀有的异常或一两次奇怪的预测;它指的是那些长期存在的变化,就像是一个新的、持久的模式。这些持续的变化可能会永久破坏你的模型,使其变得不那么有用。这就像是你的朋友偶尔突然开始说另一种语言,这可能会导致一些偶尔的困惑,但不会成为大问题。但如果他们开始一直讲另一种语言,那就会是个大问题。
此外,漂移可以分为三种主要类型:数据漂移、概念漂移和模型漂移。虽然概念漂移与数据有关,可以认为它是数据漂移的一部分,但在该领域中,概念漂移和数据漂移通常被视为分开的。让我们依次深入探讨每种漂移类型,从数据漂移开始。
探索数据漂移类型
数据漂移是指当前数据批次的某些特征与历史批次的特征存在差异。需要理解的是,数据漂移不仅仅局限于某一种类型的变化。许多从业者错误地只关注数据漂移中最为广为人知的特征变化类型——分布的变化。分布是统计学中的一个基本概念,揭示了数据集中各种变量值出现的频率。它帮助我们理解数据的性质,但这种理解是模糊的。一些常见的分布模式包括正态分布、均匀分布、偏态分布、指数分布、泊松分布、二项分布和多项分布。判断分布变化是有益还是有害可能具有挑战性,因为分布变化有时会带来好的结果,而分布不变也不一定意味着性能不会下降。分布变化与模型性能之间的关系并非总是直观的,这使得准确评估影响变得困难。因此,建议始终考虑其他更易理解、经过交叉验证测试的统计数据漂移,这些数据漂移与性能指标高度相关,而非数据 分布漂移。
统计漂移指的是统计特征的变化,例如均值、方差、相关性或偏度等,这些变化可能对模型性能产生更直接的影响。这也意味着它们之间的关系更可预测,从而可以采取更有针对性的维护措施。统计值之间的变化可以通过差异或比率来衡量。例如,如果一个深度学习模型被训练用于识别手写数字,则数字的均值强度发生统计漂移(例如,由于光照条件的变化)可能会直接影响模型的准确性。采用这种方法将使我们更好地为潜在的负面影响做准备,并保持数据分析的有效性。更广泛地说,这两者之间的选择是数据 漂移技术的一部分。
然而,漂移的相关性和可靠性处理的前提在于选择应用分布或统计漂移方法的数据类型。其他数据类型的一些示例如下:
-
数据特征漂移:当数据的基础属性或特征发生变化时,就会发生数据特征漂移。需要记住的是,漂移不仅仅涵盖实际的模型输入或输出数据变化,还可以包括与输入或输出数据相关的外部描述符或元数据。为了更具体地说明这一点,下面是一些可以监控和测量漂移的特征示例:
-
文本:某些词语、短语、情感、词数以及句子的平均长度
-
图像:物体方向、光照色调、颜色、大小以及任何样式
-
音频:声音或说话人音高、节奏、音色、语调、说话人性别、说话人口音、说话人方言和说话风格
-
-
数据质量漂移:当收集的数据质量发生分布变化时,例如缺失值、数据输入错误或测量误差,就会发生数据质量漂移。这些变化可能会影响模型进行准确预测的能力。
-
核心数据漂移:涉及图像、文本、音频以及从深度学习模型的任何层级提取的嵌入数据。该数据类型的漂移可能很难解读,而且很难找到与您关心的指标之间的关联。原始的非结构化数据本身的漂移很少与您关心的指标相关。然而,从实际用于预测的深度学习模型中提取的嵌入数据更有可能显示出与您关心的指标相关的漂移值。作为标准,实践者通常选择来自最终层输出的嵌入数据。
虽然这些数据类型类别涵盖了数据漂移的特征,但还有两种更高层次的数据漂移类型需要了解。这些更高层次的数据漂移类型决定了漂移的测量、监控和检测位置,可以被标记为漂移场景的一部分。它们如下所示:
-
协变量漂移或输入特征漂移,涉及输入特征的变化,同时保持与目标变量的关系不变。这可能源于数据收集方法、用户行为或外部因素的变化,挑战模型有效泛化的能力。它可能影响某些特征在预测目标变量时的重要性或相关性。
-
电子产品到具体标签,如智能手机,当基础的用户-产品关系保持不变时,就构成了标签漂移。
在我们理解数据漂移的基础上,接下来我们将深入探讨同样重要的概念漂移现象,关注不同用例和场景中输入数据与目标变量之间不断变化的关系。
探索概念漂移
概念漂移与特定的应用场景和数据特征密切相关。概念漂移的发生和影响并不拘泥于预定义的类型,而是基于问题背景、数据属性、时间动态、外部影响和适应策略等因素的变化。认识到这一背景依赖性对于定制有效的概念漂移检测和适应方法至关重要,以确保方法能够与每种情境的细微差异相匹配。让我们来看看一些概念漂移的例子:
-
搜索引擎算法:搜索引擎的排名算法从用户行为中学习,但用户偏好随着时间的推移而演变。曾经被认为相关的内容可能不再相关,导致相关性的概念发生变化,并改变输入数据(查询)和目标(排名)之间的关系。
-
在线广告活动:在在线广告中,用户的点击行为由于新的趋势或人口统计特征而发生变化。这导致用户偏好的变化,影响输入数据(广告展示)和目标(点击率)之间的关系。
-
医疗诊断:在医学诊断中,患者的个人资料随着人口统计数据或健康趋势的变化而发生变化。这会影响数据中“正常”和“异常”的概念,改变输入数据(患者特征)和目标(诊断)之间的关系。
最后,是时候探索最后一种漂移类型,称为模型漂移。
探索模型漂移
模型漂移仅仅涉及模型评估指标的变化,这通常需要提供真实和自然的目标,系统指标和业务指标。这个漂移是最直接可以监控和捕捉的,因为它可以直接与你关心的指标相关联。
现在,我们已经准备好探索处理漂移的策略。
探索处理漂移的策略
仅仅为已部署的模型设置漂移监控并不足以有效应对所有潜在的漂移相关问题。你需要问自己:选择的数据类型的具体漂移是否会影响模型在最关键的指标上的表现?漂移在何种情况下变得不可接受?要正确处理漂移,首先要确定对你的模型和业务最重要的漂移指标和数据类型。如果你的模型开发得当,它可能具有通用性,这是大多数机器学习从业者的主要目标。这意味着一个开发良好的模型应该能够有效应对漂移。当漂移检测和警报配置不充分考虑其影响时,可能会出现在没有实际问题的情况下触发漂移警报的风险,这可能导致时间和资源的浪费,而这些本可以用于其他更有生产力的地方。图 17.1展示了我们将要探讨的涵盖策略的高层次方法:

图 17.1 – 漂移处理方法
让我们深入探讨第一种策略,即探索漂移检测方法和策略。
探索漂移检测策略
并不是所有可能影响模型及其相关指标的漂移类型都需要监控。检测漂移有两种主要方法,如下所示:
-
基于人工洞察和领域知识估计未来漂移事件:这种方法不需要任何监控设置,依赖人工作为警报机制。假设有一个电子商务平台,使用深度学习模型向客户推荐商品,目的是增加商品购买量。在不同季节,比如万圣节或圣诞节,顾客偏好的漂移通常会发生。例如,人们在十月可能会搜索服装,而在十二月则会搜索礼物。基于这些领域知识,在每个季节来临之前,你可以预先根据预期的季节趋势调整模型的推荐,而不是测量和检测漂移。这避免了测量和检测偏好的变化,并最终可以防止性能下降。
-
使用自动化程序化测量、监控和检测特定特征或模式:假设你训练了一个深度学习模型来识别电子邮件是否是垃圾邮件,并通过真实数据进行了验证。通过分析,你发现电子邮件的平均长度影响了模型的性能。由于没有足够的数据来训练模型解决这个问题,你决定部署该模型,并监控电子邮件长度的变化。通过跟踪和比较这个程序化的电子邮件长度测量,你可以有效地检测到漂移,并采取后续行动来改进模型性能。程序化数据分布漂移检测方法包括统计检验、距离度量和分类模型,将在程序化检测漂移部分进一步探讨。
有时两者都需要,有时只需要第一种方法。在预计概念漂移发生的固定日期上进行规划,并安排相应的日期来缓解漂移,比尝试检测漂移更为合理。然而,正如前面所提到的,某些情况下你确实需要实施漂移监控和检测。
如果选择以编程方式测量、监控和检测漂移,重要的是要确保为漂移的测量和检测设置足够长的时间间隔。你需要捕捉到那些持久的漂移,它们可能会让你的模型表现不佳。这能确保我们不是在追踪一次性异常,而是在解决那些能影响模型可靠性的显著变化。为此,生产中的漂移检测应配置为批处理模式运行,不需要实时监控和检测。
对于其他监控已部署深度学习模型的指标,推荐的方案是使用 NVIDIA Triton 推理服务器、Prometheus 服务器和 Grafana 来实现实时预测、监控和告警功能。然而,对于批量预测,推荐的技术栈是使用 Apache Airflow 来调度漂移检测任务定期执行,使用 PostgreSQL 等数据库存储来自 Airflow 任务的漂移测量值,并通过 Grafana 连接到 PostgreSQL 进行批量漂移监控和告警创建。特别地,推荐使用数据库而不是 Prometheus,因为数据库更为高效,Prometheus 需要设置更多服务,占用更多资源。使用 Prometheus 可能会浪费资源,因为它是为实时使用而设计的,而在这里并不需要实时使用。可以参考 github.com/evidentlyai/evidently/tree/v0.4.4/examples/integrations/airflow_batch_monitoring 获取如何设置此方案的教程。
设置推荐的批量预测栈的前提是,输入数据必须保存在某个地方,以便 Airflow 任务能够在未来获取。数据可以以任何格式存储,通常对于实际业务用例,输入数据应已存在于如 PostgreSQL 等数据库中。如果漂移应用于预测结果,那么预测结果要么也需要保存在数据库中,要么可以作为漂移测量之前的任务,置于 Airflow 中相同的有向无环图(DAG)下,并可以定期调度执行。
接下来,为了确保能够可靠地进行漂移监控和检测,我们需要在设置之前进行一个额外步骤,即分析漂移的影响。
分析漂移的影响
为了减少漂移警报在没有实际问题的情况下被触发的风险,在部署之前对所选模型进行漂移影响分析。漂移影响分析与第十四章,分析对抗性性能部分中介绍的对抗性性能分析方法密切相关。可以采用相同的策略,使用可控的增强或收集具有目标数据特征漂移的真实世界数据进行评估。因此,可以重新探索该章节,并将相同的分析方法应用于漂移的考虑。其核心思想是确保你选择用于监控漂移的数据类型或类型的任何变化与模型性能指标之间有某种关联。换句话说,一次性进行相关性分析、对抗性性能分析或漂移影响分析!但要注意相关性不等于因果关系的问题,避免得出误导性结论。
注意
当无法增强特征时,部署后进行监控并进行影响分析可能是至关重要的。
因此,作为额外奖励,来自对抗性性能分析的发现应指导建立适当的检测阈值,这个阈值通过确定在任何额外的指标降级变得不可接受的阶段来决定。就像二进制阈值必须精细调校,以平衡二分类模型中召回率和精确度的权衡一样。这是上一章中第十六章,深度学习模型管理部分引入的护栏过滤器组件的一部分。通过数据特征阈值实现的护栏过滤器,可以降低数据在极端情况下漂移的风险。
这里需要区分介绍的两种相关方法,它们有助于应对性能降级,即带有阈值的护栏过滤器和漂移监控。护栏过滤器在每个预测请求级别上运行,而漂移则在更高层次上进行,在特定时间范围内与一批预测一起工作。它们之间的关键互利关系是,护栏过滤器有助于消除已知会导致不准确或不可靠结果的极端示例,从而减少更极端漂移的可能性和不良影响。即使通过护栏过滤器避免了极端值,测量和监控相同数据类型的统计数据仍然是有用的。
另一个需要考虑的重要方面是,部署后的模型可能会受到各种类型的漂移影响。有时候,你可以可靠地分析特定特征对期望指标的影响,而在其他情况下,即使特征是可测量的,你也可能无法分析这种影响。这种无法分析的原因可能来自于缺乏有效的增广方法来模拟该特征,或是缺乏包含目标特征的自然数据示例。当你无法可靠地分析影响时,有两种主要的选择可以考虑:
-
设置对所选数据特征统计的监控,而不包含检测组件,从而允许在随着时间的推移收集到更多数据后进行未来的分析。或者,考虑实施软警报机制,当你怀疑可能影响模型的特征出现极端或未知值时,提醒进行部署后的分析。
-
应用基于数据分布漂移的监控和检测。虽然很难预测分布变化对指标的影响,但将其作为一种更可靠的任意软警报机制仍然有价值,类似于前面提到的方式。
在制定检测策略并验证其影响后,接下来就是考虑如何解决漂移可能带来的潜在问题,并确定应对任何漂移事件的步骤。
探索减缓漂移的策略
这属于模型维护的范畴,我们采取行动以确保模型保持在正确轨道上,并在时间推移中保持有效性。以下是一些可以用于处理漂移警报的技术:
-
重新训练或微调:定期使用新数据重新训练或微调模型,融入任何模式或趋势的变化。这将帮助模型适应不断变化的数据动态,并保持其准确性。
-
防止预测高风险漂移指标范围:不要允许数据位于高风险特征范围或分布距离范围内的预测。例如,人脸识别系统应仅预测正面无遮挡、没有戴口罩或眼镜的人脸。这样可以实现针对漂移的有针对性预防,因为你接收到的数据始终会在预期的范围内。
-
人工分析:当漂移警报触发时,专家参与决策过程。专家可以审查情况,验证模型的总体预测,并提供反馈以改善模型的性能。这种方法有助于保持模型的准确性和有效性,同时也为未来的改进提供宝贵的见解。这对于处理新的、未见过的、非数值型数据(如文本数据中的新词或新的标签类别)非常有用。
通过适当的漂移影响分析、漂移检测策略和模型维护流程设置,你可以确保即使在基础数据分布发生变化的情况下,你的深度学习模型仍然保持稳健和准确。通过理解数据变化如何影响模型的性能,制定识别和量化漂移的策略,并建立清晰的模型更新和维护流程,你可以主动解决问题,保持模型的可靠性,并随着时间的推移提供一致且值得信赖的结果。
在接下来的章节中,我们将深入探讨程序化漂移检测的主题。
程序化检测漂移
在全面理解漂移类型及其影响的基础上,我们将探索程序化检测漂移的技术,深入探讨概念漂移和数据漂移的领域。掌握这些方法后,你将能够实施高风险的漂移检测组件。让我们从概念漂移开始。
程序化检测概念漂移
概念漂移涉及输入数据和目标数据。这意味着,只有当我们能够访问生产环境中的真实目标标签时,才能有效地检测部署模型的概念漂移。当你能够访问这些标签时,可以采用以下技术来检测概念漂移:
-
检查生产数据与参考训练数据的相似性:这应包括输入数据和输出数据。
-
使用模型评估指标作为代理:评估指标可以指示概念漂移或数据漂移。
-
使用基于多变量的数据漂移检测,并包括输入和目标数据:这在检测数据漂移而非概念漂移时可能不可靠。但这并不改变必须采取措施的事实,所以这是可以接受的。
接下来,我们将探索程序化数据漂移检测。
程序化检测数据漂移
程序化检测数据漂移涉及两个关键步骤:量化数据变化的类型,并基于参考或训练数据和当前数据应用检测阈值。对于基于统计的漂移,检测可以通过分析中识别的数据统计值阈值来实现。然而,对于基于分布的数据漂移,定义阈值可能会变得困难且模糊。在本节中,我们将重点介绍量化分布变化的方法。为此,可以采用以下任一方法:
-
evidently,以及它的优缺点:统计 检验类型 优点 缺点 Evidently 实现信息 Kolmogorov- Smirnov (K-S) 检验 非参数且无分布要求,使其具有广泛适用性。计算和解释速度快。 对分布尾部的差异不太敏感。假设数据是连续且一维的。 支持:数值数据类型阈值:得分 < 0.05 默认:对于数值数据,样本数 <= 1,000 卡方检验 适用于分类数据。计算和解释速度快。 需要将数据分箱,这可能是主观的。假设观察值是独立的。 支持:分类数据类型 阈值:得分 < 0.05 默认:对于标签 > 2 的分类数据,如果样本量 <= 1,000 Z 检验 适用于大样本。计算和解释速度快。 假设数据符合正态分布且已知总体方差。对于小样本不适用。 支持:分类数据类型 阈值:得分 < 0.05 默认:对于二元分类数据,如果样本量 <= 1,000 Anderson-Darling 检验 相比 K-S 检验,对分布尾部的差异更为敏感。可以通过适当的缩放用于多种分布。 假设数据是连续的。计算上比 K-S 检验更复杂。 支持:数值数据类型 阈值:得分 < 0.05 默认:N/A Fisher 精确检验 即使在小样本下也能准确。适用于分类数据。 计算密集,特别是在大样本时。仅限于 2x2 的列联表。 支持:分类数据类型 阈值:得分 < 0.05 默认:N/A Cramér-von Mises 检验 对分布的中心区域和尾部区域的差异敏感。非参数性且不依赖分布。 比 K-S 检验计算上更复杂。假设数据是连续的。 支持:数值数据类型 阈值:得分 < 0.05 默认:N/A G 检验(似然比检验) 适用于分类数据。渐近上与卡方检验等效。 需要较大的样本量才能获得准确的结果。假设观察值是独立的。 支持:分类数据类型 阈值:得分 < 0.05 默认:N/A Epps-Singleton 检验 对分布形状的差异敏感。对异常值具有鲁棒性。 计算复杂。假设数据是连续的。 支持:数值数据类型 阈值:得分 < 0.05 默认:N/A T 检验 计算和解释速度快。适用于比较两个组的均值。 假设数据符合正态分布且方差相等。当正态性假设不成立时,适用于小样本的情况不佳。 支持:数值数据类型 阈值:得分 < 0.05 默认:N/A
表 17.1 – 分布变化的统计检验
-
evidently及其优缺点:距离度量 优点 缺点 显然 实施信息 Wasserstein 距离 捕捉两种分布之间的几何差异,考虑到形状和位置。为比较分布提供了一种自然且可解释的度量标准。 计算开销较大,尤其是在高维数据中。对于离散分布或稀疏数据可能效果不佳。 支持:数值数据类型 阈值:距离 >= 0.1 默认:对于数值数据,如果样本量 > 1,000 Kullback-Leibler(KL)散度 通过测量使用一个分布编码另一个分布所需的额外位数来量化两个概率分布之间的差异。适用于连续分布,并且具有较强的理论基础。 非对称:KL(P || Q) ≠ KL(Q || P),这可能影响度量的解释。如果两个分布的支持不重叠,可能会导致结果为无限值,这使得它不太适合数据漂移检测。 支持:数值型和类别型数据阈值:距离 >= 0.1 Jensen-Shannon(JS)距离 对称度量:JS(P || Q) = JS(Q || P),使得它更适合进行比较。值范围在 0 到 1 之间,便于解释。结合了 KL 散度和互信息的优点。 在某些情况下,JS 距离可能对分布之间的小差异不够敏感。 支持:数值型和类别型数据阈值:距离 >= 0.1 默认:对于类别型数据,如果样本量 > 1,000 Hellinger 距离 有界:生成的值在 0 到 1 之间,易于解释 可能对分布之间的小差异不够敏感。 支持:数值型和类别型数据阈值:距离 >= 0.1
表 17.2 – 数据漂移的距离度量
- 分类模型区分参考数据和当前数据:需要设置一个二元阈值。虽然这严格来说不是一个分布变化的测量,但它可以视为一个近似的分布变化。
幸运的是,evidently 库提供了所有这些方法,并且有默认阈值。Evidently 是一个易于使用的工具包,提供了度量监控、数据漂移检测和数据漂移分析功能,适用于机器学习模型。
作为数据分布基础漂移检测主题的后续,介绍的任何方法都可以通过单变量或多变量方法来执行。选择这些方法的依据取决于数据的复杂性和所需的漂移检测粒度。以下是选择每种方法的建议:
-
在以下情况下使用单变量漂移检测:
-
各个变量之间的关系不显著或不是主要关注点
-
目标是从细粒度层面检测漂移,专注于每个变量单独分析
-
数据维度低或变量数量少,分析每个变量时不那么具有挑战性
-
可用于分析的计算资源或时间有限,因为单变量方法通常比多变量方法计算需求更低
-
-
在以下情况下使用多变量漂移检测:
-
多个变量之间的关系至关重要,检测这些关系中的漂移对于模型性能至关重要
-
数据具有高维度或有很多变量,导致很难单独分析每个变量
-
目标是捕捉数据漂移的全貌,考虑变量之间的相互作用
-
可用的计算资源和分析时间是充足的,因为多变量方法可能比单变量方法更消耗计算资源
-
单个变量的意义未定义,例如来自深度学习模型生成的嵌入
-
评估这些因素,以确定检测机器学习模型中数据漂移的适当方法。
最后一步是确定检测阈值。由于任何分布变化不一定意味着正面或负面的影响,因此很难通过交叉验证技术为您的数据集设置阈值。这里的思路是设定一个合理的大幅度分布变化值,至少能够引起影响变化。幸运的是,如果使用 evidently 库,它提供了默认的阈值,使我们能够在无法分析指标影响时,将此技术真正视为一个任意漂移检测器。
接下来,我们将深入探讨使用 Python evidently 库进行程序化数据分布漂移检测的简短实践实现。
使用 evidently 实现程序化数据分布漂移检测
你可能好奇的是,分布的绝对幅度是否在分布漂移计算中很重要。在这一部分,我们将探索一个简短的教程,演示如何使用 evidently,该教程展示了以下三点:
-
在分布漂移的测量中,绝对幅度和相对幅度都很重要。
-
检测到的分布漂移或高度漂移的分数不一定导致性能下降
-
分布漂移与指标性能下降的对齐
该教程将基于与 第十四章 中的 执行对抗性性能分析用于语音识别模型 部分相同的模型、数据集和数据集特征,分析对抗性性能,讲解语音识别。让我们按步骤深入探讨:
-
首先,我们将导入必要的库:
import evaluate import numpy as np import pandas as pd import syllables import torch from audiomentations import TimeStretch from datasets import load_dataset from evidently.metric_preset import DataDriftPreset from evidently.metrics import ColumnSummaryMetric from evidently.metrics import DataDriftTable from evidently.report import Report from tqdm import tqdm_notebook from transformers import Speech2TextProcessor, Speech2TextForConditionalGeneration -
接下来,我们将在 GPU 上加载数据集和语音识别模型:
ds = load_dataset("google/fleurs", 'en_us', split="validation") device = torch.device("cuda") model = Speech2TextForConditionalGeneration.from_pretrained("facebook/s2t-small-librispeech-asr") processor = Speech2TextProcessor.from_pretrained("facebook/s2t-small-librispeech-asr") model.to(device) -
我们将在这里使用词错误率(WER)性能指标,因此我们将使用 Hugging Face
evaluate库中的方法,并结合计算并返回指标分数列表的方法:wer = evaluate.load("wer") def get_wer_scores(dataset, transcriptions=None, sampling_rates=None, is_hg_ds=False, verbose=True): all_wer_score = [] for idx, audio_data in tqdm_notebook(enumerate(dataset), total=len(dataset), disable=not verbose): inputs = processor( audio_data["audio"]["array"] if is_hg_ds else audio_data, sampling_rate=audio_data["audio"]["sampling_rate"] if is_hg_ds else sampling_rates[idx], return_tensors="pt") generated_ids = model.generate( inputs["input_features"].to(device), attention_mask=inputs["attention_mask"].to(device)) transcription = processor.batch_decode( generated_ids, skip_special_tokens=True) wer_score = wer.compute(predictions=transcription, references=[audio_data['transcription'] if is_hg_ds else transcriptions[idx]]) all_wer_score.append(wer_score) return np.array(all_wer_score) -
我们将使用一个已知特征,它会影响模型的指标性能,可以通过增强进行控制,并且可以测量,这就是每秒音节数。让我们定义一个方法,获取增强结果并调用该方法来计算指标分数:
def get_augmented_samples_wer_results(all_baseline_samples, transcriptions, all_sampling_rates, rates_to_change): all_augmented_samples = [] for idx, audio_sample in enumerate(all_baseline_samples): if rates_to_change[idx] != 0: augment = TimeStretch(min_rate=rates_to_change[idx], max_rate=rates_to_change[idx], p=1.0) augmented_samples = augment(samples=audio_sample, sample_rate=all_sampling_rates[idx]) all_augmented_samples.append( augmented_samples) else: all_augmented_samples.append(audio_sample) wer_scores = get_wer_scores( all_augmented_samples, transcriptions, sampling_rates=all_sampling_rates, is_hg_ds=False) return wer_scores, all_augmented_samples -
为了正确演示即使检测到漂移时性能改进的行为,我们将使用修改过的数据集作为参考基准。让我们通过首先提取原始数据集信息来获取它:
all_syllables_per_second = [] original_dataset = [] all_sampling_rates = [] transcriptions = [] for audio_data in ds: num_syllables = syllables.estimate(audio_data['transcription']) syllables_per_second = num_syllables / (audio_data['num_samples'] / audio_data['audio']['sampling_rate']) all_syllables_per_second.append( syllables_per_second) original_dataset.append(audio_data['audio']['array']) all_sampling_rates.append(audio_data['audio']['sampling_rate']) transcriptions.append( audio_data['transcription']) -
接下来,我们获取一个扩展到原始时长三倍的音频数据集,并准备与
evidently库处理兼容的 DataFrame,从而有效地将每秒音节数减少三倍:reference_wer_scores, reference_samples = get_augmented_samples_wer_results(original_dataset, transcriptions, all_sampling_rates, rates_to_change=[3] * len(original_dataset) ) reference_df = pd.DataFrame({ "wer_score": reference_wer_scores, "syllables_per_second": [sps / 3.0 for sps in all_syllables_per_second]}) -
现在我们有了参考数据集,我们需要一个当前数据集来模拟我们在部署的模型中接收到的新数据。我们将把参考数据集的 90%修改为每秒 10 个音节,以展示从正常分布到高度偏斜分布的极端分布变化情况:
majority_number = int(len(reference_samples) * 0.9) minority_number = len(reference_samples) - majority_number majority_rates = [] for i in range(majority_number): majority_rates.append(10.0 / all_syllables_per_second[i]) current_wer_scores, current_samples = get_augmented_samples_wer_results(reference_samples, transcriptions, all_sampling_rates, rates_to_change=majority_rates + [0] * minority_number) reference_syllables_per_second = reference_df[ 'syllables_per_second'].values.tolist() current_df = pd.DataFrame({ "wer_score": current_wer_scores, "syllables_per_second": [10] * majority_number + reference_syllables_per_second[-minority_number:]}) -
现在我们既有参考数据集,也有当前数据集,让我们获取数据漂移报告:
data_drift_dataset_report = Report(metrics=[ DataDriftTable(columns=["syllables_per_second"]), ColumnSummaryMetric(column_name="wer_score")]) data_drift_dataset_report.run(reference_data=reference_df, current_data=current_df) data_drift_dataset_report.show(mode='inline')

图 17.2 – Evidently 生成的数据漂移报告
请记住,当数据集的列数少于 1,000 列时,会使用默认的 K-S 检验,这也是此处所使用的情况。在 0.05 的阈值下,检测到了漂移,并且度量指标的性能显著下降;这是理想的情况。
-
接下来,我们将创建一个模拟,其中每秒发音的音节数将是当前数据的三倍。我们将使用原始数据集,并获取
evidently的漂移报告:wer_scores = get_wer_scores(original_dataset, transcriptions, sampling_rates=all_sampling_rates, is_hg_ds=False) current_df = pd.DataFrame({"wer_score": wer_scores, "syllables_per_second":all_syllables_per_second}) data_drift_dataset_report = Report(metrics=[ DataDriftTable(columns=["syllables_per_second"]), ColumnSummaryMetric(column_name="wer_score")]) data_drift_dataset_report.run(reference_data=reference_df, current_data=current_df) data_drift_dataset_report.show(mode='inline')这将生成如图 17.3所示的报告:

图 17.3 – Evidently 报告显示相同分布但不同幅度的漂移仍被检测到
在这里,分布模式明显相同,但每组每秒音节的绝对幅度要高得多。K-S 检验仍然成功地将其检测为漂移,因为它使用累积分布差异,展示了分布漂移方法的多功能性。至此,我们已经完成了教程!evidently提供了更广泛的度量指标,包括数据质量统计和模型评估指标。它还内置支持监控数据漂移,并检测数据类型,如嵌入和文本。请务必单独探索这些功能。此外,考虑深入研究每种方法的细节,发现分布漂移技术中你从未想到过的新行为。
除了 Evidently 之外,还有一个值得注意的开源库可以用于以编程方式处理漂移,这也是我们接下来要探讨的内容的一部分。
比较和对比 Evidently 与 Alibi-Detect 库在漂移检测中的应用
在本节中,我们将对比和分析两种流行的深度学习模型漂移检测库:Evidently 和 Alibi-Detect。这两种库都提供用于监控和检测数据漂移的工具,但它们在功能上有所不同。通过了解每个库的优缺点,你可以选择最适合你需求的漂移检测工具,帮助你在深度学习模型中实现数据漂移的检测。
Evidently 具有以下特点:
-
提供一个易于使用的工具包,用于监控数据漂移,包括对各种数据类型(如嵌入和文本)的内置支持
-
提供一整套用于衡量漂移的指标,包括统计测试、距离度量和分类模型
-
支持单变量和多变量漂移检测方法,能够灵活处理不同类型的数据和使用场景
-
提供简单直观的界面,用于生成漂移检测报告和可视化
-
支持通用评估指标和数据质量指标
另一方面,Alibi-Detect 具有以下特点:
-
一个 Python 库,包含多种漂移检测、离群值检测和对抗性检测技术
-
支持在线和离线检测器,用于表格数据、文本、图像和时间序列
-
声称支持 TensorFlow 和 PyTorch 模型,开箱即用
Evidently 和 Alibi-Detect 都是深度学习模型中漂移检测的强大库。根据你的具体需求,你可以选择 Evidently 或 Alibi-Detect 作为首选的漂移检测库。在典型情况下,Evidently 可以作为事实上的标准库。然而,在撰写本书时,如果你处理的是没有可用嵌入模型的非表格数据、需要离群值检测,或需要 Evidently 不提供的统计测试,那么 Alibi-Detect 是一个更合适的选择。
总结
在本章中,我们探讨了漂移的概念,它会随着时间的推移影响已部署深度学习模型的性能。我们介绍了三种类型的漂移——概念漂移、数据漂移和模型漂移,并讨论了有效应对这些问题的策略。包括处理漂移的策略,诸如自动化编程检测和人工领域专家预测,量化漂移的策略,以及有效减缓漂移的策略。我们了解到,基于统计的漂移应始终优于模糊的数据分布漂移。我们还了解到,以批处理方式在定期间隔内监控漂移对于确保深度学习模型的持续成功至关重要。最后,使用evidently库,我们展示了如何在实际教程中实现编程数据分布漂移检测,并理解了能够塑造你对数据分布漂移方法思考方式的行为。这些知识可以应用于多个行业和领域,例如医疗、金融、零售和制造业,在这些行业中,保持深度学习模型的准确性和性能对于高效决策和优化业务流程至关重要。
本章标志着我们深入探讨深度学习生命周期每个组件的完成。在下一章中,我们将探索一个付费平台——DataRobot,如何在一个易于使用的用户界面中涵盖深度学习生命周期的关键组件。
第十八章:探索 DataRobot AI 平台
本章我们将重点讨论 DataRobot AI 平台,这是一款付费软件平台,提供强大的工具集来支持深度学习应用案例。DataRobot 允许用户简化机器学习生命周期中的复杂阶段。它为数据科学家、工程师和研究人员提供了一个直观的界面,帮助他们在项目和业务中利用机器学习的强大功能。当我们深入了解 DataRobot 的工作原理时,你将学习到它如何简化并加速复杂深度学习模型的创建、训练、部署和管理。凭借为自动化和易用性设计的功能,它使用户能够专注于真正重要的事情——从他们的机器学习应用中提取重要的价值。
我们的探索将重点介绍 DataRobot 的关键功能,强调其在深度学习解决方案演进中的潜力。DataRobot 致力于为机器学习应用案例提供自动化、协作和可扩展性的结合,使其在深度学习领域也成为一个值得关注的工具。
具体来说,我们将涵盖以下内容:
-
高级概述:DataRobot AI 平台提供的功能
-
使用 DataRobot 准备数据
-
使用 DataRobot 执行建模实验
-
部署深度学习蓝图
-
管理已部署的深度学习蓝图
技术要求
本章我们将探讨一个实用的话题,通过使用 DataRobot 部署的模型进行预测。我们将使用 Python 3.10,并需要安装以下 Python 库:
-
datarobotx==0.1.17 -
pandas==2.0.3
代码文件可以在 GitHub 上找到:github.com/PacktPublishing/The-Deep-Learning-Architect-Handbook/tree/main/CHAPTER_18,数据集可以从 www.kaggle.com/datasets/dicksonchin93/datarobot-compatible-house-pricing-dataset 下载。
此外,需要一个付费或免费试用账户才能访问 DataRobot。订阅试用账户的步骤如下:
-
访问 DataRobot 网站:
www.datarobot.com/trial/。 -
在网页右侧的 Start For Free 界面中填写你的凭证,并点击 Submit 按钮。
高级概述:DataRobot AI 平台提供的功能
DataRobot AI 平台通过模型监控和模型维护工具提供数据摄取、数据准备、数据洞察、模型开发、模型评估、模型洞察与分析、模型部署和模型治理,这些工具相互无缝配合。虽然 DataRobot 简化了深度学习生命周期,但需要注意的是,规划阶段仍然需要人工输入,以定义项目的目标和范围。此外,你仍然需要利用易于获得的洞察、报告和结果。最终,这意味着这样的平台是一个可以协助任何机器学习从业者的工具,而不是替代数据科学家、机器学习工程师、机器学习研究人员或数据分析师。可以把像 DataRobot 这样的 AI 平台看作是强大的计算器,可以帮助你快速而准确地解决复杂的数学问题。但就像计算器不能代替你的思考一样,DataRobot 也无法替代数据科学家、工程师、分析师或研究人员的专业知识和创造力。
DataRobot 提供的某些工具被设计为可扩展、可组合和灵活,允许你添加自己的代码或组件,它们不会将你束缚在平台仅提供的功能上。此外,一些组件覆盖了广泛的方法,因此你不需要担心进行任何定制化。实际上,这意味着你可以在可靠的速度下执行项目,同时仍然保持定制化的能力。
在我们深入了解 DataRobot 提供的组件之前,还有一些关键信息可以帮助你了解该平台的功能:
-
平台托管选项:DataRobot AI 平台提供云托管应用。如果你的企业有数据隐私和安全方面的顾虑,DataRobot 还提供将 AI 平台作为私有托管实例,在你自己的基础设施中进行托管的选项。
-
可扩展性和协作性:无论你是单个用户还是团队,DataRobot 都设计为可以根据你的需求进行扩展。DataRobot 提供的大多数工具组件都可以与多个用户共享,这使得不同用户之间的协作成为可能。
-
datarobot,可以通过PyPI安装。几乎你在 Web UI 中看到的所有内容都可以通过 API 接口和 Python API 客户端访问。此外,Notebooks 功能提供了一个灵活和互动的环境,在这个环境中,数据科学家可以手动进行复杂的数据分析、创建机器学习模型,并通过 Python API 客户端轻松地与 DataRobot 其他功能一起原型化数据操作。它通过允许默认使用传统的 Python 代码来增强用户体验。 -
过渡到以用例为中心的资产管理:在第一章中介绍的机器学习(ML)或深度学习(DL)生命周期是一个迭代过程。也就是说,一个深度学习用例会涉及大量的数据、数据版本、模型开发实验、使用已部署模型的应用程序和创建的笔记本。DataRobot 中的工作台功能旨在通过在单一界面中管理许多与用例相关的资产,支持这一过程,使得通过用例实现价值变得更加容易。然而,在编写本书时,工作台并未全面支持传统的、独立管理的 DataRobot 项目提供的所有功能。未来将会更新以支持完整的功能套件。你可以使用 DataRobot Classic 功能单独管理项目。图 18.1 展示了典型机器学习从业者如何导航平台的示例流程:
![]()
给 PD:该图片已发送进行重绘。
图 18.1 – 示例工作台工作流
现在,我们已准备好深入了解深度学习用例相关的支持功能,首先从数据准备组件开始。
使用 DataRobot 准备数据
平台提供的第一个部分是数据准备组件。DataRobot 通过提供一系列功能来简化数据摄取、清洗、转换和集成过程。让我们详细了解这些功能。
深度学习模型开发的数据摄取
在 DataRobot 中,深度学习模型的开发始于数据摄取这一关键步骤。这个过程允许你直接从各种来源导入数据,包括云存储(如 AWS S3)、Google Cloud Storage、本地文件或数据库(如 PostgreSQL、Oracle 和 SQL Server)。该平台接受多种文件格式,包括 CSV、XLSX 和 ZIP 文件。此外,平台支持通过二级数据集作为输入数据类型的图像、文本、文档、地理空间、数值、类别和汇总类别数据。对于目标数据类型,平台支持数值型、类别型和多标签数据类型,以及无目标数据的无监督学习。这为回归、二元分类、多类分类、多标签分类、无监督异常检测和无监督聚类奠定了基础。
值得注意的是,图像数据类型、文本数据类型和文档数据类型是 DataRobot 中构建深度学习用例的核心非结构化输入数据类型。图像数据和文档数据通过底层编码为 base64 字符串来支持。然而,待导入的数据集本身可以是已压缩的图像或文档文件夹,其中文件夹名称为类别或目标名称。文本数据自然可以以表格数据的形式存在于某一列中,并以 CSV 等格式编码。此外,如果存在二级数据集或日期时间特征列,DataRobot 会自动创建有用的特征。
有两种路径可以导入数据:
-
通过项目创建,这与模型开发过程紧密相连。
-
通过AI 目录功能,你可以独立共享数据集,之后可以用于在用例或项目中独立创建实验。
让我们探索第二种方法,因为它是一种更负责任且可靠的数据管理方式,用于模型开发。
使用 DataRobot 导入图像和文本数据集
我们将处理一个使用多模态数据预测房价的案例,数据包括图像数据、文本数据、日期数据、分类数据和数值数据。让我们开始一步步的教程:
- 通过点击+ 创建新用例按钮开始创建用例,按钮如图 18.2所示。图 18.2中的页面也是你进入 DataRobot web 应用并通过app.datarobot.com登录后的着陆页面:

图 18.2 – 在 Workbench 中创建用例的界面
- 现在,点击右上角的DataRobot Classic,然后点击界面左上角的AI 目录选项卡。接着,点击界面左上角的添加到目录按钮,再点击本地文件,如图 18.3所示:

图 18.3 – 在 DataRobot 中将数据集添加到目录的界面
-
上传代码仓库中提供的
trulia_pricing_dataset.zip文件。该数据集是一个 ZIP 文件,包含一个 CSV 文件,其中包含原始数据,此外还有原始图像文件,这些图像文件通过其在 ZIP 文件中的相对路径映射到 CSV 中的某一列。从这里,AI 目录中的上传数据集可以独立管理并单独共享。 -
创建完成后,再次进入右上角的 Workbench,进入我们创建的用例,并将用例重命名为合适的名称,如
房价预测。然后,点击界面右上角的添加新按钮下拉菜单,点击添加数据集,如图 18.4所示:

图 18.4 – 工作台界面中的定价预测用例
- 从数据注册表页面选择
trulia_pricing_dataset并点击添加到用例,如图 18.5所示:

图 18.5 – 将数据集添加到用例
现在,我们已经准备好进入 DataRobot 的 EDA 部分。
数据的探索性分析
DataRobot 提供了一个两步体验,用于执行最多 500MB 数据子集的探索性数据分析(EDA)。第一步是 EDA,在确定项目或实验类型之前执行,第二步则在之后进行。提供了标准的 EDA 技术,包括数值和分类数据的直方图,分类数据前 50 项的频率分布,重复计数、缺失值计数、伪缺失值检测、过度零值检测、目标泄漏、数值汇总、离群行、单变量特征与目标的相关性,以及一个衡量互信息的特征关联矩阵。特别是对于图片和文本数据,DataRobot Classic 在第 1 步显示数据的总体样本,并在第 2 步按目标值或值范围组进行排序。
图 18.6展示了 DataRobot Classic 中的房价预测数据集的 EDA 2 可视化,左侧图片显示了按目标分组的图片样本,右侧图片则显示了重复特征的界面:

图 18.6 – 图片的 EDA 1 和 EDA 2
现在,让我们继续进行房价数据集的 EDA 教程。
在 DataRobot 中实际执行 EDA
我们将按步骤继续进行 EDA 教程:
- 在用例中的数据选项卡下点击
trulia_pricing_dataset实体,你将看到图 18.7所示的视图,界面可以水平和垂直滚动:

图 18.7 – 工作台中房价数据集的样本数据预览 EDA 界面
如果你滚动查看整个表格,你会发现数据集由 68 个特征组成,其中 52 个特征被认为是有用的。最显著的是一个有效的图片列,以及另外 23 个原本应该是图片的列,但由于链接没有正确包含,实际上只是一些链接。它还包含五个有用的文本列,分别为事实、简短和完整的地址、房屋特征以及房屋的一般描述。
- 如果你点击左上角的特征按钮,你将看到图 18.8所示的界面,其中展示了数据集的简单统计信息:

图 18.8 – 房价数据集的特征
- 现在,我们需要设置预测目标。这里的用例是预测房价,因此点击目标特征框中的
价格。然后你会看到如图 18.9所示的界面:

图 18.9 – 在工作台中选择目标
- 然后,点击开始建模按钮,开始快速建模过程。如果你从 DataRobot Classic 界面导航到数据标签,你将能够看到在后台计算的单变量重要性。这会根据所选目标对特征进行单变量信息量排名。这个过程在图 18.10中用绿色条形图表示:

图 18.10 – DataRobot Classic 数据标签显示单变量重要性
接下来,我们将探索 DataRobot 在连接数据集源并执行 EDA 后,如何支持数据预处理。
数据预处理用于深度学习模型开发
DataRobot 允许你使用其预处理功能对数据进行转换。此功能支持通过数据连接将数据集添加到源(如 Snowflake)。你可以创建一组打算应用于整个数据集的转换,这些转换被称为食谱。这些转换首先会在实时样本上进行测试,以确保其准确性。一旦食谱完成,它将被发送到数据源并执行,生成最终的输出数据集。此功能还允许你选择性地将转换后的数据集直接保存到数据源中,并通过数据注册表(AI Catalog)获取输出。
DataRobot 支持广泛的转换操作,包括以下内容:
-
从同一连接实例合并数据集
-
对数据集特征应用数学聚合
-
使用标量子查询、标量函数或窗口函数计算新特征
-
根据指定的值和条件过滤行
-
去重行以移除重复项
-
查找并替换特定特征值
-
重命名数据集中的特征
-
从数据集中删除选定的特征
注意
DataRobot 不提供数据标注工具来从零开始创建标签,它依赖于一个假设:原始的业务数据已经被记录并保存在某个地方。使用外部标注工具,如 LabelBox,来协作和可靠地为深度学习用例标注数据。
由于我们使用的数据集是本地数据集,因此这里不会实际探讨数据预处理部分。请访问docs.datarobot.com/en/docs/workbench/wb-dataprep/wb-wrangle-data/wb-add-operation.html进一步了解此话题。
接下来,我们将探讨 DataRobot 如何执行建模实验或项目。
使用 DataRobot 执行建模实验
DataRobot 目前提供两种方式来执行建模实验:DataRobot Classic 和 Workbench。Workbench 是一个在用例下管理实验的地方,重点是更加无缝地从用例中提取价值,而 DataRobot Classic 是原始的 AutoML 体验,其中建模实验被称为项目。在这里,项目或建模实验包含相同的组件,这些组件包括建模机器学习、收集模型洞察与预测洞察以及进行一次性批量预测。我们将深入探讨这三个组件。
深度学习建模
DataRobot 提供以 有向无环图(DAG)形式呈现的建模配置和任务,这些图被称为 蓝图。图中的每个节点分组为以下内容:
-
输入数据:输入节点可以是任何支持的输入数据类型。
-
数据预处理任务:它们包括数据规范化、标准化、缺失值填充以及任何数据预处理逻辑。你也可以执行选择具体列进行操作的任务。此外,执行预测后处理的技术也在这里归类。作为特征转换的预训练网络也在这里归类。
-
建模任务:它们包括所有格式的任何模型,模型会生成预测。你还可以将一个模型任务作为中间节点的一部分,在这里,模型的预测可以通过堆叠方法用于后续的建模任务。堆叠方法输出来自 k-折交叉验证策略的组合折外特征,该策略在第八章,《探索监督式深度学习》的深度学习训练数据划分部分中介绍。这对于训练神经网络并以非过拟合方式在推理阶段提供新特征非常有用。
从全面来看,根据数据类型,支持的深度学习特定任务类型如下:
-
图像任务:视觉 AI 特性是封装与 DataRobot 中图像相关的所有内容的产品功能,支持预训练特征提取器、微调特征提取器和预测器,涉及以下网络:Darknet、EfficientNet-B0、EfficientNet-B4、Preresnet10、Resnet50、Squeezenet、mobilenet-v3-small 和 EfficientNetV2-S。特别是对于预训练特征提取器,提供了上述网络的修剪变体,这些变体在提高推理速度的同时不会降低准确性。对于特征提取器,DataRobot 提供了一种开箱即用的方式,可以从预训练网络中提取低、中、高及最高级别的特征,并可以根据使用案例进行调整。此外,支持图像增强任务,这些任务可以在实验执行前和蓝图训练后通过高级调优功能进行配置,其中训练过的蓝图可以使用新参数重新训练。在配置图像增强过程中,将提供增强图像的显示预览,这将在接下来的实际操作部分中进行展示。
-
文本和文档的模型类型:DataRobot 支持多种文本和文档处理模型,包括词形还原器、预训练的词性标注器、词干提取器、FastText 嵌入、带停用词的 TFIDF、针对英语语言的预训练 TinyBERT 特征提取器、针对英语语言的预训练 Roberta 特征提取器,以及支持多语言的预训练 MiniLM。请注意,DataRobot 为文本所做的策略是,所使用的停用词和预训练模型将取决于在 EDA 样本中检测到的语言。
-
通用模型:DataRobot 提供了多种通用模型,如带残差的 MLP 和不带残差的 MLP、自动特征交互学习(AutoInt)、使用 Hyperband 进行 MLP 的神经架构搜索、自归一化残差 MLP 与训练计划以及自适应训练计划。
DataRobot 会根据数据集的特点、自动驾驶、快速、手动或综合模式的建模策略,自动确定将包含在实验中的蓝图。手动模式不会运行任何蓝图,而是由你决定运行哪个蓝图。快速模式、自动驾驶模式和综合模式可以视为分别需要最快、中等速度和最慢时间完成的模式。综合模式作为完成最慢的模式,会运行不同的蓝图或额外的蓝图,这些蓝图可能会长时间运行,如大型深度学习模型。
DataRobot 使用一种建模策略,逐渐淘汰模型,以找到最佳的探索和运行时平衡,从而构建最佳模型。这涉及创建一组样本量较小的蓝图,并移除较弱的模型。然后,顶级蓝图会使用更大的样本量依次进行训练。这个过程通过识别第二个精简的特征列表,仅保留最具信息量的特征,继续进行。最终,使用这个特征列表对最佳模型进行训练。对于图像,所有蓝图都使用一个预训练的 CNN 模型作为基础模型。当最终的蓝图使用最终的样本量和精简的特征列表构建完成时,最佳模型将在一个更大且更耗时的预训练网络上进行重训练。这种方法有助于识别最有效的特征,并确保最终模型在准确性和效率上得到优化。
另一个关键的建模功能是 DataRobot 的偏差和公平性功能。它使用户能够通过定义受保护的属性、评估各种公平性指标并比较不同子群体的模型表现来构建和评估公平的 AI 模型。在撰写本书时,受保护的属性必须是分类值。如果通过显示高级选项启用该功能,平台会自动检测潜在的偏见,提供缓解策略,并允许用户在整个模型开发过程中监控公平性。通过纳入这些功能,DataRobot 促进了负责任的 AI 部署,确保模型符合伦理指南并为所有用户和子群体提供公平的结果。
如果你想尝试一些不属于开箱即用的深度学习任务的工作,可以利用自定义任务,这些任务可以在建模实验或项目中共享和使用。自定义任务允许你定义自定义逻辑来进行数据预处理或建模逻辑。基于此功能,组合式机器学习(Composable ML)功能允许你灵活地重构和重新排列蓝图 DAG。这些功能使你能够利用更多开箱即用的常用方法,并能够在实验中尝试任何自定义模型。
DataRobot 通过按需工作队列执行所有任务,例如训练蓝图、计算模型洞察和计算预测。每个用户将获得自己分配的工作者数量。使用深度学习模型进行训练和预测可能需要较长时间。幸运的是,DataRobot 既有 CPU 工作者也有 GPU 工作者,深度学习模型可以在 GPU 工作者上运行,以加速运行时间。
在评估方面,DataRobot 使用嵌套交叉验证,并且绝不使用测试数据进行训练过程中的验证。DataRobot 支持的评估指标非常全面,可以参考 docs.datarobot.com/en/docs/modeling/reference/model-detail/opt-metric.html。这些指标和训练好的蓝图随后会以排行榜形式呈现,蓝图会根据所选指标进行排名。
然而,蓝图之间的比较远不止是比较在同一数据集上训练的蓝图。数据集可以不同,实验设置也可以不同,关联的洞察也会有所不同。此时,模型比较功能有助于弥补这个差距,并允许在单一用例下比较许多蓝图设置。
最后要提到的是,大多数与建模相关的设置,如权重列、分区策略和优化的评估指标,都可以在高级选项功能下进行配置。接下来我们将继续教程,执行一个模型实验。
在 DataRobot 中实际执行建模实验
让我们通过逐步过程深入了解 DataRobot 的实际建模,从上一个教程继续:
- 在你启动建模过程后,在工作台中你将看到以下界面,DataRobot 会显示平台在等待蓝图加载时的操作情况:

图 18.11 – 等待蓝图在工作台中加载
- 在等待蓝图生成并完成训练后,在工作台中,你将能够看到左侧排序好的训练蓝图,如图 18**.12 (a)所示,我们可以星标两个模型,以便更全面地比较它们:

图 18.12 – (a) 显示带有分数的排名蓝图,(b) 显示数据集对比功能
-
通过点击对比标签,你将能够在多个实验中,按评估指标、数据集、蓝图类型等方面比较两个星标模型的表现,正如图 18**.12 (b)所示。
-
通过点击在Gamma Deviance指标下表现最好的模型,我们可以在蓝图下拉菜单中查看该模型的蓝图结构,如图 18**.13所示。该蓝图是一个多模态蓝图,最终模型使用 XGBoost,并接受来自分类、地理空间、数值、图像和文本变量的转化输入。

图 18.13 – 最佳模型蓝图图示
- 现在,让我们看看是否可以对验证、交叉验证或留置分区的指标得分进行手动改进。由于默认实验建模模式是快速试点,并且在撰写时不能在 Workbench 中重新运行另一种建模模式,让我们通过点击实验界面左上角的查看实验信息来手动选择存储库中可用的蓝图,这将带您到图 18**.14中的界面:

图 18.14 – 实验信息中的蓝图存储库选项卡
-
现在,搜索所有 Keras 模型和精调图像模型,勾选复选框,并点击右侧的训练模型按钮,以训练更多需要更长时间执行的蓝图。
-
作为额外的建模步骤,让我们将图像增强添加到先前标记为最佳模型的现有模型中。我们可以通过导航到DataRobot 经典模型选项卡,并点击验证分区上最佳模型上的星号。在蓝图下的评估选项卡中点击高级调整子选项卡。这将带您到图 18**.15中显示的界面:

图 18.15 – 最佳模型的评估选项卡下的高级调整界面
- 现在,滚动到Image Augmentation List选项卡,点击创建新列表,如图 18**.16 (a)所示。将模糊、遮挡、水平翻转、垂直翻转、每个原始图像的新图像、概率、位移、缩放和旋转配置为默认设置。点击预览增强按钮,你将看到像图 18**.16 (b)中那样的图像预览。现在点击保存为新列表,设置你的名称,并点击创建增强列表。最后,点击开始调整,这也显示在图 18**.16 (a)中:

图 18.16 – 高级调整中的图像增强配置
等到新调整的蓝图完成训练,你将会得到一个表现更好的蓝图!
这里完成的步骤仅涵盖 DataRobot 支持的建模过程的一小部分。请务必探索功能,如偏差、公平性缓解和评估、可组合 ML、自定义任务、许多其他参数的高级调整以及时间序列建模。
到这个阶段,我们仅使用评估指标作为模型比较的唯一反馈形式。在大多数关键使用情况下,仅使用指标比较蓝图是不够的。在接下来的部分中,我们将发现如何收集模型和预测见解,全面比较蓝图。
收集模型和预测见解
值得注意的是,DataRobot 提供了以下洞察,这些洞察与包含深度学习模型任务的蓝图相关:
-
特征影响:这是一种多变量分析,帮助确定数据集中不同特征的重要性,揭示哪些变量对模型预测有最强的影响。
-
特征效果:这是一个工具,帮助你理解数据集中每个特征如何影响模型的预测。它提供了一个清晰、易于解释的可视化,展示每个特征与模型输出之间的关系。
-
激活图:这些是显示输入图像中最有责任做出最终蓝图预测的区域的可视化。它涵盖了作为预测器的神经网络、中间特征提取器,甚至是中间建模器。
-
预测解释:这些是用于解释模型输出的技术,突出显示每个输入特征对特定预测的贡献。SHAP(Shapley 加性解释)和 XEMP(基于扰动的扩展示例模型解释)是两种受支持的流行方法。对于图像,使用图像激活图;对于文本解释,使用与模型无关的方法提供基于单词的重要性分数。
-
词云:这是一种可视化技术,用于表示文本数据集中单词或短语的频率,其中单词的大小表示其重要性或频率。
-
图像嵌入:这是在低维空间中对图像的可视化,捕捉了图像的基本特征。它来源于支持的 CNN 模型的输出。
-
ROC 曲线:这是一个图表,展示了二分类器的诊断能力,显示不同阈值设置下的真实正例率与假正例率的关系,帮助选择最佳阈值。除了曲线,还增加了混淆矩阵和一个可选的利润曲线功能。利润曲线是一种工具,通过绘制不同阈值下的利润(或其他性能指标),帮助优化分类模型的阈值。
-
神经网络可视化工具:这是一个工具,允许用户可视化神经网络的架构,展示各层、神经元以及它们之间的连接。
-
训练仪表盘:这是一个工具,提供一个易于使用的界面,展示了重要的损失曲线以及神经网络模型的每个训练周期或迭代的各种度量。
-
蓝图可视化工具:这是一个功能,提供了整个蓝图的综合视图,展示了创建机器学习模型时所涉及的数据处理、特征工程和建模步骤。
现在,让我们继续之前的教程,探索其中的一些功能。
实际上在 DataRobot 中收集洞察
在 DataRobot Classic 体验中的每个蓝图下,您可以探索所有的洞察功能。在 Workbench 中,正在进行工作以增加全面的洞察体验,迄今为止,特征影响、提升图和残差洞察已可用。让我们以逐步方式深入讲解教程中的洞察部分:
- 从点击
图像 4栏开始使用,即使它只是 URLs。接下来的Home Id栏应该被移除:

图 18.17 – 特征对最佳模型的影响
- 接下来,我们将通过点击
浴室来探索最佳蓝图的特征效果。效果图显示,随着浴室数量的增加,价格通常会增加,这很有道理:

图 18.18 – 最佳蓝图的特征效果
- 接下来,点击
描述,它也是一个重要的贡献特征。点击值栏下的符号打开弹出模态窗口,您可以查看该特征的文本说明,如图 18.19所示。两个说明都很清晰且合理:

图 18.19 – 最佳蓝图的预测说明
- 现在,让我们点击被负面归因的
nice。然而,cozy被负面归因是有道理的,因为它通常指代小型单元。

图 18.20 – 最佳蓝图的词云重要性归因
- 接下来,点击激活图子标签页,查看图 18.21所示的界面:

图 18.21 – 最佳蓝图的激活图
对于图像激活图,您需要注意逻辑上不合常理的区域。可以提出像“为什么模型在看天空?”或“为什么模型在看草地?”这样的问题。同时,可以进一步提问“修剪整齐的草坪和价格有关系吗?”对于这个使用场景,涉及到很多视觉组件,使得很难判断什么不合常理。聚焦于草地仍然可能是有意义的,但至少可以放心的是,模型至少没有在看没有任何贡献的天空。此外,预测和实际过滤器可以帮助您辨别成功示例的行为与失败示例的行为,这对于形成模型识别的模式的心理图像非常有帮助。另一个问题是,图像列在表示房屋的哪一部分时没有标准化。标准化有助于您获得更好的预测性能。
- 最后,你可以单独导出洞察报告,甚至更好的是,下载合规报告文档,它提供了一个离线的一站式文档,包含所有洞察。你可以通过点击合规性标签页并点击创建报告按钮(如图 18.22所示)来完成。文档有一个默认结构,但你可以根据需要设计报告的确切结构。代码库中提供了一个示例文档。

图 18.22 – 蓝图下的模型合规文档界面
这样,我们就完成了关于收集洞察的教程。不过,请注意,这并不是一个全面的关于此用例的洞察收集方法。因此,务必测试更多类型的洞察,例如提升图和图像嵌入,并在收集洞察时进行更多模型改进的迭代。
在我们进入 DLOps 部分之前,先来探索一下如何在不部署模型的情况下进行批量预测。
批量预测
在某些用例中,部署不是必须的,因为可以通过自定义触发器或一次性事件以定期的方式异步进行预测。这时,批量预测功能就派上用场了。批量预测允许你上传数据,选择性地计算预测结果(并可附加预测解释),完成后可以下载结果。
此步骤可以在 DataRobot Classic 和 Workbench 中完成。对于 DataRobot Classic 的图形界面体验,导航到预测标签下的进行预测子标签页。然后,你就可以上传你希望用来生成一次性预测的数据集。该界面如图 18.23所示:

图 18.23 – 批量预测功能界面
仍然会有一些用例需要通过单个样本进行实时预测。这引出了下一部分——讨论 DataRobot 如何管理蓝图的部署。
部署深度学习蓝图
DataRobot 允许通过实验或项目中的训练蓝图直接部署模型,我们将在下一个实际操作部分中详细探讨。然而,对于更高级的用户,平台还允许通过 requirements.txt 文件部署自定义模型。一旦上传,用户就可以创建、测试并将自定义推理模型部署到 DataRobot 的集中部署中心。这些自定义模型支持不同类型的模型,包括回归、分类和非结构化类型,其中输入和输出可以是各种类型。
为确保自定义模型的可靠性和兼容性,DataRobot 提供了一个全面的测试套件,供自定义模型工作坊使用。自定义模型测试套件涵盖了广泛的评估,包括以下内容:
-
启动检查:确保自定义模型能够构建且自定义模型服务能够无误启动
-
空值插补检查:这验证了对缺失值的处理
-
副作用检查:确保在批量数据中预测的一行数据与单独预测同一行数据时产生相同的预测结果
-
预测验证:这确认了预测的正确性
-
性能检查:这评估了模型的效率和速度
-
稳定性检查:这评估了模型的一致性和可靠性
-
持续时间检查:这衡量了完成各种任务所需的时间
通过运行这些测试,你可以在部署前验证自定义模型的性能、稳定性和预测准确性。使用监督式自定义模型的一个额外好处是,它可以与训练数据关联,这样就可以计算预测解释并衡量和监控数据漂移。一旦你的自定义模型完成并经过测试,就可以将其与 DataRobot 中的其他蓝图一起部署,使其成为高级用户的多功能且强大的工具。要部署模型(无论是自定义模型还是本地 DataRobot 模型),前提是你需要选择你想要的部署可靠性。可靠性级别包括以下几种:
-
低:适用于非关键、实验性或低优先级的用例,在这些情况下,偶尔的停机或性能下降是可以接受的。此选项提供最少的资源和基础设施冗余。
-
中等:适用于需要在成本和性能之间平衡的中等重要的用例。此级别提供比低级选项更好的资源和冗余,但在高峰期负载时,仍可能会经历一些停机或性能下降。
-
高:推荐用于要求高可用性和高性能的重要用例。此级别提供增加的资源、基础设施冗余和更快的响应时间,以确保在重负载下也能保持一致的性能。
-
关键:专为任务关键型应用设计,最大化可用性和性能至关重要。此选项提供最高级别的资源、冗余和响应时间,以确保在任何条件下几乎无停机时间并保持最佳性能。
选择适当的可靠性级别可以让 DataRobot 根据你的需求配置适当的服务器机器类型和基础设施来托管你的模型。由于你的组织签订的部署协议有一定限制,选择适当的可靠性级别可以确保你不会在超出组织部署限制后支付过多费用,或者确保你不会超出部署限制。换句话说,你必须管理部署过程中产生的成本。让我们继续之前的教程,部署表现最佳的蓝图,该蓝图没有经过验证或保留分区训练。
实际在 DataRobot 中部署蓝图
部署蓝图就像去蓝图下的 Predict 标签下的 Deploy 子标签,然后点击 Deploy model 按钮一样简单。接着,你需要选择部署可靠性,默认值为低。然后,在相同位置再次点击 Deploy model 按钮。第一个 Deploy model 按钮的界面如 图 18.24 所示:

图 18.24 – 部署蓝图
就这样!我们已经成功通过点击两个按钮在 DataRobot 中部署了一个模型。接下来,我们将讨论 DataRobot 如何管理已部署的蓝图。
管理已部署的深度学习蓝图
在本节中,我们将讨论 DataRobot 如何通过提供全面的模型利用、监控和维护工具,帮助用户有效地管理他们的深度学习模型。DataRobot 关注无缝集成,允许用户在云端或本地基础设施上部署 AI 应用,管理预测输出,并使用自定义指标和警报监控模型性能。此外,平台支持数据漂移检测,并提供再训练功能,以持续改进模型。我们将详细探讨这些功能,展示 DataRobot 如何帮助用户高效管理深度学习模型,并确保其在整个生命周期中的最佳性能。
通过 DataRobot 中的模型利用进行管理
用户可以通过多种方式访问他们的模型,如 API 调用、Python 接口,或 DataRobot 制作的名为 base64 格式的应用程序。DataRobot 还方便将预测结果直接存储到数据库中,从而简化了将模型输出纳入现有工作流程或应用程序的过程。此外,DataRobot 允许安排定期执行批量预测,以便按指定的频率和时间自动执行。
总体而言,DataRobot 中的模型利用组件简化了深度学习模型和机器学习模型的一般应用流程,使其对各个领域的用户更具可及性和效率。
现在,让我们继续之前的教程,使用 DataRobot HTTP 客户端库获取预测。
实际使用 DataRobot 中部署蓝图的预测
在本节中,我们将继续上一个教程,并使用datarobotx Python 客户端库通过已部署的模型生成预测。在每个部署下,DataRobot 都包含了低级示例代码,可以向部署发送实时prediction-api请求。然而,在本教程中,我们将使用一个简单易用的高级库datarobotx,该库简化了prediction-api请求的发送。让我们开始逐步操作:
-
让我们先导入所需的库:
import datarobotx as drx import pandas as pd -
接下来,我们将基于部署 ID 初始化部署实例。你需要在此处将
deployment_id替换为自己的部署 ID:deployment_id = "YOUR_DEPLOYMENT_ID" d = drx.Deployment(deployment_id) -
接下来,我们将在 DataRobot 上下文类中设置 API 令牌和端点 URL。你需要在此处设置自己的令牌:
drx.Context( token="YOUR_API_TOKEN", endpoint="https://app.datarobot.com/api/v2", ) -
现在,我们将加载代码仓库中提供的
trulia_one_row.csv房价数据框,并使用初始化的部署实例进行预测。最后,我们展示预测结果:one_row_df = pd.read_csv("trulia_one_row.csv") prediction_results = d.predict(one_row_df, max_explanations=3) print(prediction_results.to_json(indent=2))这将产生以下结果:
{"prediction":{"0":1020288.625},"explanation_1_feature_name":{"0":"Price Sqr Ft"},"explanation_1_strength":{"0":0.4690563465},"explanation_1_actual_value":{"0":"$359\/sqft"},"explanation_1_qualitative_strength":{"0":"+++"},"explanation_2_feature_name":{"0":"Description"},"explanation_2_strength":{"0":0.3055447842},"explanation_2_actual_value":{"0":" The table is set in this large Plan 4 with its top two levels of interactive living spaces for educating and engaging. Four bedrooms suit home-based entrepreneurs, stepchildren and a widowed parent who can come together in an expansive gathering room. An elongated deck invites open-air grilling and entertaining. A side-by-side garage adds valuable storage. "},"explanation_2_qualitative_strength":{"0":"++"},"explanation_3_feature_name":{"0":"Bath"},"explanation_3_strength":{"0":0.2212744913},"explanation_3_actual_value":{"0":3.0},"explanation_3_qualitative_strength":{"0":"++"}}
本教程到此结束。接下来,我们将深入了解 DataRobot 如何为已部署的模型实现模型监控。
通过在 DataRobot 中进行模型监控进行治理
在 DataRobot 中,模型监控是一个重要组成部分,允许用户跟踪其已部署的深度学习模型的性能和健康状况。平台提供了多项功能,确保模型随着时间的推移保持最佳性能:
-
数据漂移检测:DataRobot 持续监控输入数据分布的变化,识别任何与原始训练数据的偏差。此功能帮助用户了解模型是否因基础数据发生变化而可能面临准确度下降的风险。
-
模型性能监控:用户可以跟踪其模型随时间变化的性能。包括以下内容:
-
准确性:可以通过将实际目标值与预测值进行比较来进行监控。实际目标值可以在做出预测后随时发送到部署,但前提是必须设置关联 ID 并返回,以将实际目标与历史预测请求连接起来。
-
公平性:确保 AI 模型在生产环境中继续提供公平和无偏的预测。这里同样需要一个关联 ID,因为它与准确性相关的性能指标相关。部署模型的公平性功能的关键方面包括:
-
公平性指标追踪:平台追踪各种公平性指标,如差异影响、人口统计平衡和机会平等,使用户能够评估其模型在受保护属性不同子群体中的公平性。
-
警报和通知:如果检测到偏差或差异,DataRobot 可以配置为发送警报和通知,确保用户及时了解在模型生命周期中可能出现的任何公平性问题。
-
-
-
谦逊规则:用户可以设置基于不良条件执行的操作。支持的条件包括预测中的不确定性、异常输入值或范围,以及低观察区域。支持的操作包括记录触发器、用定义的预测覆盖预测结果,以及抛出错误。这增强了用户对模型预测的整体信心,减少了模型基于低信心水平的预测、分布外数据或低观察数据点做出错误决策的风险。
-
部署服务健康监控:包括总预测次数、总请求次数、在定义的时间间隔内发出的请求数、聚合响应时间(例如中位数)、聚合执行时间(中位数)、每分钟调用的中位数峰值负载、数据错误率、系统错误率、消费者数量以及缓存命中率。
-
部署通知:有关部署变化的通知,可以选择通知所有变化或仅通知关键变化。
-
自定义指标追踪和警报:DataRobot 允许用户定义和监控特定于其使用案例的自定义性能指标。用户可以设置警报,当某些阈值被达到时通知他们,从而确保及时响应任何与性能相关的问题。
现在,让我们实际探索一下 DataRobot 提供的模型监控界面。
实际监控在 DataRobot 中已部署的蓝图
点击已部署模型的服务健康标签,你将能够查看整体服务健康监控。如图 18.25所示。此外,你可以在已部署模型下的各自标签中查看数据漂移、准确性、谦逊、公平性和自定义指标的仪表盘。而通知则默认配置为发送至所有部署活动,并可以根据偏好进行配置。

图 18.25 – 已部署模型的服务健康状况
接下来,我们将讨论用户如何维护已部署模型的性能,并确保模型能够持续提供价值。
通过 DataRobot 进行模型维护的管理
DataRobot 中的模型维护是管理深度学习模型的一个关键方面,确保它们在整个生命周期内持续提供准确和可靠的结果。平台提供了多个功能来促进有效的模型维护:
-
挑战者模型和模型替换:DataRobot 允许用户创建并比较替代模型(称为挑战者模型)与当前已部署模型的表现。通过评估这些挑战者模型的表现,用户可以识别潜在的改进,并决定是否需要用性能更好的替代模型替换现有模型。一旦确定了更好的模型,它将被称为冠军模型,并替换掉之前的模型,保持相同的部署 ID,确保顺利过渡到更好的模型。
-
模型版本控制:DataRobot 跟踪并管理模型的不同版本,允许用户在需要时轻松恢复到之前的版本。此功能确保用户可以保留模型历史记录,并在不同版本之间进行性能对比。
-
再训练和再训练策略:用户可以将通过已部署模型接收到的数据,包括输入数据和延迟的目标值,注册到 AI 目录中。这使得模型能够使用最新的数据进行再训练。此外,DataRobot 的再训练策略提供了一种管理和自动化模型更新过程的方法,确保已部署的模型保持相关性并维持最佳性能。再训练策略的关键方面包括以下内容:
-
可定制触发器:用户可以定义用于再训练的特定触发器,例如数据漂移、性能下降或预定的时间间隔,以便在满足特定条件时自动启动再训练过程。
-
数据集成:再训练策略促进了新数据无缝集成到模型更新过程中,确保模型训练时使用最新和最相关的信息。
-
自动化再训练和部署工作流:DataRobot 自动化了整个再训练过程,从数据摄取和预处理到模型构建和验证,简化了模型更新工作流,并减少了人工工作量。用户可以选择保持相同的模型及其参数,保持相同的模型并进行超参数优化,或者从自动驾驶模式中选择最佳模型。再训练后的模型会使用性能指标自动与新数据进行评估,帮助用户评估更新后的模型性能,并确定其是否准备好重新部署。一旦再训练后的模型达到您希望实现的性能标准,DataRobot 将帮助其无缝部署,最小化中断,替换掉现有模型。通过采用再训练策略,DataRobot 简化并自动化了模型更新过程,帮助用户确保他们部署的 AI 模型在新数据和洞察可用时保持准确、相关和高性能。
-
通过深入了解 DataRobot 支持的功能,并通过实践教程,我们获得了关于 DataRobot 如何利用深度学习方法处理和分析数据,包括非结构化数据和结构化数据的重大见解。现在,让我们来看看一些实际的案例,这些案例展示了该技术的能力,并由对 DataRobot 充满热情的客户分享了他们的经验。
探索一些客户成功案例
DataRobot 使众多组织通过实施深度学习解决方案取得了显著成功,特别是在处理非结构化数据(如文本和图像)方面。虽然大多数成功案例仍然保密,但我们很幸运能有一些客户热情地分享了他们鼓舞人心的经验,展示了深度学习在各个行业中具有的变革潜力。其中一些显著的成功案例包括:
-
联想,这家领先的科技公司,成功在其巴西笔记本电脑制造厂实施了 DataRobot 的视觉 AI,以改善质量控制并提高生产力。该视觉 AI 系统通过自动化比对笔记本电脑上的识别标签与其相应的物料清单,将标签验证的准确率从 93%提高到了 98%。此实施不仅减少了人工标签过程中的错误,还对交货时间、客户满意度以及制造商的法律风险产生了积极影响。
-
OYAK 水泥,土耳其领先的水泥制造商,成功利用 DataRobot 的 AI 解决方案优化了其制造流程,从而降低了成本和 CO2 排放。通过实施 AI 辅助的过程控制,OYAK 将替代燃料的使用量提高了七倍,减少了近 2%的总 CO2 排放,并节省了约 3900 万美元的成本。该公司还能够更高效地预测和防止机械故障,提高了整体运营效率和环境可持续性。
-
AUTOproff 成功实施了 DataRobot AI 平台,其中包括处理车辆图像数据的视觉 AI 能力,开发了其定价机器人来进行自动化的汽车价值估算。这个 AI 驱动的解决方案自动化了 55%–60%的所有估算,从而提高了定价准确性,并显著缩短了生成报价所需的时间。因此,评估人员可以专注于更为稀有的车辆,提高了他们的效率。定价机器人的成功在 AUTOproff 的欧洲扩展中发挥了关键作用,使公司能够迅速适应新市场。这最终提高了客户满意度,并优化了业务运作。
获取更多关于最新成功案例的信息,请访问www.datarobot.com/customers/。
当我们走到本章的尾声时,如果您有兴趣亲自试用 DataRobot AI 平台,并且尚未拥有访问权限,您可以订阅免费的试用版。这将使您能够亲身体验 DataRobot 所提供的强大工具和自动化功能,试用期为 30 天(截至 2023 年 9 月 28 日),帮助您专注于从深度学习应用中提取显著价值。
总结
本章探讨了 DataRobot AI 平台,并展示了 AI 平台能为您提供的普遍好处。DataRobot 简化了机器学习生命周期中的复杂阶段,为数据科学家、工程师和研究人员提供了直观的界面。通过利用像 DataRobot 这样的 AI 平台的潜力,用户可以加速创建、训练、部署和管理复杂的深度学习模型,专注于从机器学习应用中提取显著价值。
DataRobot 为机器学习应用场景提供自动化、协作和可扩展性。DataRobot 支持多种数据类型和先进功能,如偏差与公平性缓解、可组合机器学习、定制任务、先进调优和时间序列建模等。DataRobot 还使用户能够在基于云的或本地的基础设施上部署 AI 应用程序,管理预测输出,监控模型性能,并维护在挑战者模型、模型版本控制、再训练和再训练策略等功能中实现的模型。
本章展示了 DataRobot AI 平台的各种功能和能力,但并未全面覆盖该平台提供的所有内容。此外,随着公司不断发展以满足现实世界数据科学的需求,未来可能会添加任何尚未支持的功能。欲了解更详细的信息,可以参考官方文档 docs.datarobot.com/。
总之,像 DataRobot 这样的 AI 平台为深度学习应用提供了强大的解决方案,简化并加速了深度学习生命周期。然而,它们并不是数据科学家、工程师、分析师或研究人员的专业知识和创造力的替代品;相反,它们是帮助从业人员快速且准确地解决复杂问题的工具。
当我们进入下一章时,我们将深入探讨大语言模型的世界,探索它们的潜力、挑战以及如何创造有效的解决方案。在前面所有章节的基础上,我们将揭示如何利用 LLM 的强大功能来处理复杂的语言任务,并创建先进的、具备上下文意识的应用程序。
第十九章:构建 LLM 解决方案
大型语言模型 (LLMs) 已经彻底改变了 自然语言处理 (NLP) 和 人工智能 (AI) 领域,展现出在处理各种任务时的卓越多功能性。然而,要实现它们的全部潜力,需要解决一些挑战并开发有效的 LLM 解决方案。本章将为您揭开构建 LLM 解决方案的神秘面纱,重点介绍内存、问题解决能力、自主代理和增强性能的高级工具等关键方面。我们将专注于基于检索增强的语言模型,这些模型能够提供上下文相关的信息,探讨它们的实际应用以及进一步改进它们的方法。此外,我们还将揭示挑战、最佳实践和评估方法,以确保 LLM 解决方案的成功。
在这些基础概念的基础上,本章将为您提供必要的知识和技巧,帮助您创建强大的 LLM 解决方案,满足您的特定需求。通过掌握构建 LLM 解决方案的艺术,您将更好地准备好应对复杂的挑战,优化性能,并在各种现实应用中释放这些多功能模型的真正潜力。
具体来说,我们将涵盖以下主题:
-
LLM 解决方案概述
-
处理 LLM 解决方案中的知识
-
评估 LLM 解决方案
-
识别 LLM 解决方案中的挑战
-
应对 LLM 解决方案的挑战
-
利用 LLM 构建自主代理
-
探索 LLM 解决方案的应用场景
LLM 解决方案概述
LLM 在回答问题、机器翻译、语言建模、情感分析和文本摘要等多种任务中表现出色。它们能够生成非结构化文本,但可以被引导生成结构化输出。LLM 解决方案利用这一能力,并通过知识库中的自定义数据来创造有针对性的、有价值的结果,服务于组织和个人。通过合理优化流程和客观提升输出质量,LLM 解决方案可以释放 LLM 生成内容的真正潜力,使其在各类应用中更加强大和实用。
随着预训练世界知识的 LLM 逐渐变得更加普及,这在使更多人群能够获得这些好处方面发挥了重要作用。得益于各种 LLM 提供商和开源平台,组织和开发者现在可以更轻松地采纳并将 LLM 集成到他们的工作流程中。知名的 LLM 提供商,如 OpenAI(GPT-4 或 GPT-3.5)、Microsoft Azure、Google 和 Amazon Bedrock,提供预训练模型和 API,能够无缝集成到各种应用程序中。此外,Hugging Face 平台通过提供丰富的开源模型库,使 LLM 更加易于访问。Hugging Face 不仅提供了广泛的预训练模型和微调技术,还培养了一个积极的社区,持续贡献于 LLM 的改进。
随着组织和个人利用 LLM 的强大功能来完成日常任务和用例,至关重要的是要确定如何有效利用定制知识。这一考虑确保了 LLM 能够针对特定需求得到最优利用;这一点将在LLM 解决方案中的知识处理部分进一步探讨。通过充分利用 LLM 日益增加的可达性和多功能性,组织和个人能够释放这些强大模型的全部潜力,推动创新并改善结果。
尽管大语言模型(LLMs)具有令人印象深刻的能力,但在解决它们未被设计用来处理的复杂问题时,仍面临一些限制。以下是其中的一些限制:
-
无法访问最新事件的信息
-
倾向于虚构事实或生成模仿的虚假信息
-
理解低资源语言的困难
-
缺乏进行精确计算的数学能力
-
对时间流逝的无意识
为了克服这些限制并增强 LLM 的解决问题能力,可以通过加入以下组件来开发先进的解决方案:
-
实时数据集成:通过将 LLM 连接到实时数据源,如 API、数据库或网络服务,模型可以获取最新的信息并提供更准确的回应。
-
现有工具集成:将现有工具和 API 集成到 LLM 架构中,可以扩展其能力,使其执行一些单独模型难以或无法完成的任务。
-
具有不同人格和背景的多个代理:开发一个多代理系统,每个代理具有独特的人格和背景,可以帮助解决不同问题情境下的挑战。这些代理可以协作、共享信息并提供更全面、可靠的解决方案。
图 19.1 显示了一种架构,展示了可以在 LLM 解决方案中应用的不同方法和技巧,本章将介绍这些内容:

图 19.1 – LLM 解决方案架构
在接下来的几个部分,我们将更全面地深入探讨此 LLM 解决方案架构中列出的各个组件以及 LLM 解决方案的整体内容。我们将从如何处理 LLM 解决方案中的知识开始。
处理 LLM 解决方案中的知识
域知识是创建 LLM 解决方案的关键,它提供了解决特定问题所需的背景信息和理解。这最终确保了解决方案得出的答案或行动是准确的且有帮助的。域知识需要作为上下文包含在内,作为参数化记忆、非参数化记忆的一部分,或两者的结合。参数化记忆指的是在 LLM 中学习到的参数。非参数化记忆指的是外部知识库,如文档、文章或摘录列表,可以根据需要选择注入为 LLM 上下文的一部分。这个过程也被称为上下文学习方法、知识检索或信息检索。
非参数化的外部知识可以通过以下任一方式提供给 LLM:
-
作为交叉注意力机制中的潜在条件:潜在条件涉及从外部知识生成潜在特征向量,并将其作为键值向量的一部分输入到注意力机制中,该机制在第六章《理解神经网络转换器》中已介绍,同时原始输入作为查询向量传入。此方法通常需要对网络的解码器部分进行某种形式的微调,尤其是在编码器-解码器的转换器架构中。理想情况下,微调过程将构建一个解码器,使其能够推广到目标外部潜在特征的领域,并能够关注各种信息。此方法允许将任何数据模态作为外部知识进行包含。值得注意的是,发布的研究论文中的检索增强生成(RAG)和检索增强转换器(RETRO)方法[1][2]采用了这种方法。
-
作为 LLM 输入提示的一部分:这是一个简单的过程,不需要任何微调,但仍然可以从中受益。这种方法为将任何自定义领域知识应用于 LLM 提供了最低的入门门槛。然而,这种方法仅支持以能够有效表示为文本数据的数据模态表示的知识,如文本、数值、类别和日期数据。值得注意的是,发布的研究论文中的检索增强语言模型预训练(REALM)方法[3],特别用于预训练,并未作为最终训练模型的一部分使用。
两种方法都需要建立一个知识库,如图 19.2所示,并且需要一个知识检索组件来从知识库中检索信息,如图 19.1所示:

图 19.2 – 构建知识库
表 19.1 简要总结了 REALM、RETRO 和 RAG 方法:
| 知识 检索方法 | 检索器训练 | 检索集成 |
|---|---|---|
| RAG | 使用冻结的基础网络进行微调 | 带有交叉注意力的潜在调节器 |
| REALM | 完整的端到端训练 | 特别地将其预先加入提示中,不使用模板 |
| RETRO | 使用冻结的基础网络进行微调 | 带有交叉注意力的潜在调节器 |
表 19.1 – 与 LLM 方法的检索集成简短概述
知识库要求将文本数据预处理成适当的逻辑块或段落。这些段落随后通过训练好的变换器模型转化为嵌入向量。最后,构建最近邻索引,使得能够高效地从知识库中检索相关信息。最近邻索引可以是简单的 KNN 算法,用来计算提示嵌入向量之间的原始距离,或者是近似 KNN 算法,近似计算距离。然后,索引和逻辑文本段落将作为知识库的一部分,可以用于检索。执行检索的方法可以根据不同策略有所不同,但最简单的形式是通过生成提示的嵌入向量,并使用索引返回知识库中与之最接近的前k个文本段落。这些前k个最接近的文本段落可以作为 LLM 提示的一部分或作为潜在调节器包含进去。
对于将最相关的文本切块作为提示的一部分的方式,制作一个可以插入特定内容的提示模板是标准做法,并有助于合理地组织提示中的信息。这可以像使用引导性文本Context:一样简单,接着是检索到的相关文本切块,并在提示模板的上下文部分之前和之后插入换行符。
尽管研究论文通常会提出涵盖检索过程各个方面的已发布方法,但将构建和使用知识库的每个组件视为独立的、可互换的部分是很有帮助的。这允许在特定情况下选择最合适的组件,从而具有更大的灵活性。此外,尽管存在一种已发布的方法被称为 RAG,但值得注意的是,在实践中,RAG 这一术语通常用来描述将知识检索与 LLM 集成的通用方法,而不仅仅指该特定方法。接下来,我们将简要介绍可以根据用例自由修改的三大基于方法的组件。我们还将选择帮助简化这些组件实现的协调工具。
探索文本切块方法
文本切块过程会影响 LLM 上下文利用效率和生成结果的质量。选择合适的切块方法取决于以下因素:
-
用于生成嵌入向量的嵌入模型:不同的预训练嵌入模型在文本切块时可能有不同的要求或限制。两个这样的要求是支持的上下文大小和预训练期间使用的典型文本上下文大小。
-
预期提示所需信息的粒度:预期提示所需的详细程度或粒度可能会影响文本切块方法的选择。根据具体的使用案例,方法应能够将文本切分成合适且简洁的片段,从而为 LLM 提供必要的信息,以生成准确、简洁且相关的响应。
-
用于构建知识库的文本数据的性质:文本数据本身的特征也可能影响文本切块方法的选择。例如,如果文本数据由长段落或文档组成,可能更适合采用将文本拆分成较小切块或章节的方法。另一方面,如果文本数据已经被组织成逻辑段落,则可能更倾向于选择能够保留这些段落的方法。此外,如果文本数据是 Python 代码,使用代码方法对文本进行切块可能更为合适。
有几种方法可以进行文本切块,包括句子、段落、实体、主题和章节切块。这些方法帮助将文本组织成可以被 LLM 处理的有意义单元。一个值得注意且易于使用的方法是来自 LangChain 库的递归切块方法。该方法通过递归地使用有序文本分隔符列表、最大切块大小和切块之间的重叠百分比来调整切块的粒度。最大切块大小应根据嵌入模型支持的上下文大小量身定制,以确保生成的切块能够有效处理。与此同时,加入重叠百分比有助于最小化错过关键信息的风险,特别是那些可能位于切块边界的信息。
许多基于递归切块方法的文档特定切块方法是通过指定适当的文本分隔符的有序列表创建的。具体来说,截至langchain==0.0.314,已经为 Python 代码创建了递归方法(PythonCodeTextSplitter),为 Markdown 文档创建了MarkdownTextSplitter类的递归方法,以及为 LaTeX 格式文本创建了LatexTextSplitter类的递归方法。
接下来,让我们深入探讨嵌入模型的选择。
探索嵌入模型
嵌入模型在为 LLM 解决方案生成知识库中起着至关重要的作用。这些模型负责将文本的语义信息编码为向量表示,然后这些向量用于从知识库中检索相关信息。
一个提供文本嵌入模型性能洞察的基准是 text-embedding-ada-002。
在选择用于生成知识库的嵌入模型时,必须考虑模型大小、嵌入维度和序列长度等因素。传统的嵌入模型,如 GloVe,提供高速度,但可能缺乏上下文意识,导致较低的平均得分。另一方面,像 all-mpnet-base-v2 和 all-MiniLM-L6-v2 这样的模型在速度和性能之间取得了平衡,提供了令人满意的结果。为了获得最佳性能,像 bge-large-en-v1.5、ember-v1 和 e5-large-v2 这样的更大模型主导了 MTEB 排行榜,所有这些模型的大小为 1.34 GB。
需要注意的是,嵌入模型的选择取决于具体的任务和使用的数据集。因此,深入探索 MTEB 排行榜的不同选项,并考虑知识库生成过程的要求,有助于选择最合适的嵌入模型。
MTEB 通过其丰富的数据集和评估指标集合,为 NLP 领域的研究人员和实践者提供了宝贵的资源。通过利用 MTEB 提供的洞察,开发者可以在为 LLM 解决方案选择嵌入模型时做出明智的决策。
在我们深入探索知识库索引类型之前,必须记住,分块方法和嵌入模型的选择决定了知识库的构建方式。这两个组成部分在 LLM 能够高效地检索和利用知识方面发挥着重要作用。现在,让我们更深入地了解知识库索引类型,并学习它们如何提升 LLM 解决方案的效率。
探索知识库索引类型
知识库索引是大规模语言模型(LLM)解决方案中检索机制的支柱。它是实现高效查找相关信息的组成部分。虽然实现这个索引的方法有很多种,但它们的目标都是提供一种快速高效的方式,从知识库中基于输入提示检索最相关的文本片段。
构建知识库索引有多种选择。它们从手动代码实现,到使用各种向量数据库库、服务提供商和插件都有。以下是一些可选方案:
-
faiss是一个用于高效相似度搜索稠密向量的库,scipy是一个用于计算成对距离的库。这允许自定义,但可能需要更多的努力和专业知识,并且需要更大的内存分配。 -
服务提供商:各种云服务提供商提供向量数据库服务,包括 Pinecone、Chroma、Vespa 和 Weaviate。这些服务处理了管理向量数据库的复杂性,提供了可扩展且强大的解决方案,可以轻松地集成到你的 LLM 架构和解决方案中。
-
支持向量计算的数据库工具:传统的数据库工具,如 MongoDB、Neo4j、Redis 和 PostgreSQL,通过插件提供向量计算支持。如果你已经在技术栈中使用这些工具,并希望利用它们的功能来构建知识库,这可能是一个不错的选择。
-
插件:还有一些插件可以直接从 LLM 服务提供商获取,例如 ChatGPT,它们可以帮助构建和维护知识库。
选择正确的知识库索引类型取决于你的具体需求,比如知识库的大小、检索需求的复杂性以及你可用的资源。在做出选择时,要考虑可扩展性、集成的简便性、成本以及对知识库控制的程度。如果你的知识库已经足够大,建议只考虑使用实际数据库技术或声称使用这种技术的向量数据库。如果你的知识库比较小,例如六位数范围内,通过 Python 计算单次提示的原始距离计算时间少于 1 秒!接下来,我们将简要探索 LLM 解决方案的协调库。
探索 LLM 解决方案的协调工具
构建 LLM 解决方案的过程可以通过使用特定工具进行简化。开源的协调库,如 LangChain 和 LlamaIndex,在这个过程中发挥了重要作用。这两种工具简化了设置知识库、集成 LLM 和管理检索机制等任务。总的来说,协调工具显著减少了 LLM 解决方案的复杂性和开发时间。
除了开源的协调工具外,还有一些付费选项可供选择,这些选项提供高级功能和支持。包括 DataRobot、Microsoft Azure、IBM Watson、LangSmith、OpenAI 和 Google Vertex AI。这些平台提供了广泛的预构建模型、集成和工具,能够简化整个流程,从数据摄取到模型部署和监控。
在继续探索这些工具和方法的同时,建立强大的评估方法至关重要,以衡量这些组件对你的解决方案的影响,确保它达成预期目标。我们将在接下来的部分深入探讨这些方法。
评估 LLM 解决方案
评估 LLM 解决方案是充分发挥其潜力并确保其在各种应用中有效性的关键步骤。通过实施一套全面的评估方法,组织可以更好地评估 LLM 解决方案的性能、准确性和整体质量,同时考虑相关的成本、安全标准的遵守情况,以及对用户的潜在负面影响。换句话说,这样做为你提供了有价值的见解,帮助做出有根据的决策。为了实现全面评估,我们可以将评估方法视为定量衡量或定性衡量的一部分。让我们按这些组别深入探讨评估方法。
通过定量指标评估 LLM 解决方案
定量指标可以在提供的评估数据集中进行汇总,能够提供一种更快速、全面和客观的衡量方式,用于比较多个 LLM 解决方案设置。以下是一些定量指标的例子:
-
理解和流利度相关的指标:Flesch 阅读难易度、Coleman Liau 指数和 SMOG 可读性。
-
基于事实的指标:任何使用知识库提供的事实进行推理的指标:
-
事实一致性:指的是将生成的文本与知识库中陈述的事实进行比较。需要注意的是,相关的事实可能并不总是出现在知识库中。这个指标也被称为抽取性指标。要衡量事实一致性,可以使用语义相似度,侧重于文本含义上的差异,或者使用词汇相似度,强调文本中词汇的匹配。
-
事实相关性:这是指所提供的事实的相关性,而不考虑 LLM 生成的内容。只有在你有排名的相关文档标签时,才可以实现这一点。
-
-
生成文本的相关性/准确性指标:该指标评估由大语言模型(LLM)生成的文本与理想真实情况的相关性和准确性。可以通过相似度度量或自我评估技术来计算。自我评估可以进一步细分为以下几个方面:
-
有访问令牌概率:通过计算对数概率的平均值来评估生成文本的质量。较高的对数概率表明模型对其输出更有信心,意味着相关性和准确性较高。
-
没有访问到令牌概率:SelfCheckGPT是一种可以评估生成文本的方法,无需依赖令牌概率。该方法利用 LLM 的能力评估其生成内容的质量,提供了一种替代的相关性和准确性衡量方式。
-
-
运行时指标:生成文本所需的时间、处理的令牌数等。
-
成本指标:生成的令牌数、API 调用成本、托管成本等。
-
防护措施违规指标:违反预定义标准的输出百分比。防护措施的例子包括有毒内容水平和仇恨言论程度。
-
对抗性性能指标:衡量处理对抗性输入的性能。这些指标在 第十四章 中有更为详细的介绍,分析 对抗性性能。
-
偏见和公平性指标:用于评估生成文本中的偏见的定量度量。这些在 第十三章 中有更为详细的介绍,探索偏见 与公平性。
-
任何监督式分类或回归指标:可以应用于 LLM 解决方案的结果或结果行动。
请注意,像生成文本相关性和事实一致性度量这样的定量方法,通过使用相似度度量来比较两组文本,并不像监督模型的度量(如准确度)那样可靠。这些指标应谨慎对待。此外,定量指标的一个附加好处是,它们可以用于程序化地监控已部署的模型。接下来,我们将深入探讨定性手动评估。
通过定性评估方法评估 LLM 解决方案
定性方法涉及人类反馈和手动评估,补充了定量度量,并提供了对 LLM 性能的全面理解。当没有参考的真实数据集时,它有时也是唯一可用的评估方法。以下是一些定性 LLM 解决方案评估方法的示例:
-
人类反馈评分:这是用户对生成响应的评分或排名,用于评估其有效性和相关性。示例包括文本的语法和连贯性。
-
生成文本相关性评估:这涉及手动评估生成文本与给定上下文或提示的相关性。
-
预测解释:评估生成文本或预测背后的推理过程,这有助于识别 LLM 解决方案中的潜在偏见或错误逻辑。
-
伦理与法律合规性:通过手动审查确保生成的文本符合伦理和法律指南。
通过结合定量指标和定性手动评估,组织可以更深入地了解 LLM 的表现,并识别潜在的改进领域。通常,应该将 LLM 解决方案视为与任何监督式机器学习项目没有区别,并像评估监督式机器学习项目一样,严格评估它们。这种全面评估 LLM 解决方案的方法不仅确保了一致的表现和合规性,还帮助将这些强大的模型与特定的需求和目标对齐,从而推动创新并改善各种应用中的成果。
识别 LLM 解决方案中的挑战
尽管 LLM 具有令人印象深刻的能力,但在解决复杂的现实世界问题时,它们面临许多挑战。在本节中,我们将探讨 LLM 解决方案面临的一些挑战,并讨论可能的应对方式。我们将按高层次的群组来探讨这些挑战,如下所示:
-
输出和 输入限制:
-
LLM 只是生成文本:文本输出可以为许多企业提供价值。然而,许多其他用例需要以完全不同的格式进行预测和推荐。
-
LLM 的上下文大小有限:问题在于,随着输入大小的增大,训练和预测所需的计算资源会呈指数级增加。因此,上下文大小通常保持在一到三千个标记的范围内。这个问题应该只在需要长上下文的用例中普遍存在,因为对于大多数用例,几千个上下文大小应该足够。
-
LLM 是文本特定模型:默认情况下,其他数据模式不被支持。
-
重复检索信息:从知识库中检索到的信息可能高度相关,但却是重复且数量庞大的。由于 LLM 的上下文大小是有限的,当多个信息片段作为上下文时,可能会因为重复而占用大部分上下文限制配额,从而引发风险。
-
-
知识和 信息相关挑战:
-
无法访问最新信息:LLM 可能不了解最近的事件或发展,导致它们提供的信息过时或不准确。
-
处理低资源语言:LLM 在理解和处理数据或资源有限的语言时可能会遇到困难。
-
对时间进程的无知:LLM 可能无法理解时间的概念,这会在处理与时间敏感的信息时引发混淆。
-
信息丧失:研究表明,LLM 更倾向于关注句子的开头和结尾,而不是中间部分,因此会丧失放在中间的最重要信息。
-
单一索引失败:当 LLM 因其训练数据的限制而缺乏关于特定主题或领域的足够知识时,就会出现这个挑战。例如,如果你询问 LLM 关于一个新开张的本地餐馆,而这个餐馆没有出现在它的训练数据中,LLM 可能会提供有限或不相关的信息。
-
从文档中检索不完整的内容:当从分段句子中检索到正确的文档,但实际所需的内容位于同一文档中检索到的部分下方时,LLM 可能无法提供用户所需的完整或准确的信息。
示例:在一个关于软件安装过程的文档搜索中,LLM 检索到了提及安装的部分,但实际的逐步说明位于文档的下一部分。因此,用户只获得了一个概述,而没有得到进行正确安装所需的详细信息。
-
在上下文中使用无关信息:LLM 可能会将其上下文中的无关信息作为其输出的依据,本质上模仿或回响在上下文中出现的观点,即使这些观点不适用于当前的情况或不合适。这种现象被称为“拍马屁”,可能导致误导或无用的回应。
-
全球知识库总结任务无法准确执行:检索过程无法了解其请求的知识库类型,因此无法有效地执行全球总结任务。
-
-
准确性 和可靠性:
-
幻觉:LLM 可能会生成看似合理但并非基于事实的虚假或误导性信息。这一现象被称为幻觉。
-
缺乏数学技能:LLM 通常无法进行精确计算或解决复杂的数学问题。这一问题相对广为人知,且根据不同的视角来看,稍显有争议。
-
模仿虚假:这些是 LLM 生成的虚假陈述,因为它们模仿了训练数据中常见的误解。由于模型从其训练数据中学习,它可能无意中再现广泛存在但错误的信念。例如,如果许多人认为某种特定的食物会引发某种特定的疾病,LLM 可能会生成类似的陈述,尽管从科学角度来看并不准确。
-
非模仿虚假:这些是由于模型未能完全实现其训练目标而产生的虚假陈述,包括幻觉现象,即看似合理但实际上不正确的陈述。例如,一个 LLM 可能会生成关于某个历史事件的陈述,而该事件根本未曾发生,但对于不了解该事件的某些人来说,这个陈述听起来可能很有说服力。
-
-
运行时性能问题:LLM 的运行时间可能较慢。此外,向其添加知识库后,整个过程可能变得比原来更慢。
-
伦理影响和社会影响:LLM(大规模语言模型)的广泛采用和部署带来了若干伦理影响和社会影响。由于这些模型从大量数据中学习,它们可能无意中继承训练数据中的偏见,导致偏颇的输出,延续刻板印象,或传播虚假信息。此外,LLM 可能生成内容,这些内容可能无意中促进有害行为、仇恨言论或侵犯隐私问题。使用 LLM 解决方案时涉及以下伦理挑战:
-
偏见与公平性:确保 LLM 在分析贷款申请和确定信用资质时不表现出偏见或歧视特定用户群体,如种族、性别、年龄或其他受保护属性。考虑这样一个情况,银行使用 LLM 分析贷款申请并确定信用资质。LLM 是基于历史数据训练的,这些数据可能对某些族裔群体存在偏见。因此,LLM 可能会以更高的比率拒绝这些群体的贷款申请,即使申请人有良好的信用评分。
-
隐私问题:LLM 可能在其输出中无意生成个人可识别信息(PII)或敏感数据,这引发隐私问题和潜在的法律问题。考虑这样一个情况,一个医疗机构使用 LLM 为其客户生成个性化的健康建议。LLM 可能会在生成的建议中无意包含特定患者的姓名和医疗状况,然后这些信息被公开分享,违反了患者的隐私。
-
错误信息和虚假信息:LLM 可能会生成误导性或虚假信息,这可能导致错误信息和虚假信息的传播。考虑这样一个情况,新闻机构使用 LLM 自动总结和发布新闻文章。模型无意中生成了一个误传原始故事的摘要,导致关于重要商业并购的错误信息传播。
-
安全性:确保由 LLM 生成的内容符合道德准则、法律法规和社区标准,同时避免推广有害或冒犯性内容。考虑这样一种情况,电商平台使用 LLM 为卖家生成产品描述。LLM 可能会创建促销潜在有害产品的描述,如召回的商品或违反安全规定的商品,从而使平台面临法律和道德问题。
-
透明度与可解释性:确保 LLM 的决策对用户和利益相关者是透明、可理解和可证明的。考虑这样一个情况,保险公司使用 LLM 评估风险并确定客户的保费。客户收到显著提高的保费并要求解释增加的原因。LLM 的决策过程本身是不透明且难以理解的,这使得公司难以提供清晰和合理的解释。
-
现在我们已经确定了这些挑战,让我们继续下一节,我们将探讨克服这些限制的潜在解决方案和策略。
处理 LLM 解决方案中的挑战
解决 LLM 面临的棘手挑战对于释放其全部潜力并使其成为解决现实问题的可靠工具或助手至关重要。只有通过解决这些挑战,LLM 的解决方案才能客观有效地形成。在本节中,我们将深入探讨各种互补策略,帮助我们解决这些挑战并提升 LLM 在高级问题类型中的性能。我们将从输出和输入限制开始。
解决输出和输入限制挑战
导航输出和输入限制挑战对于释放 LLM 的全部潜力至关重要,使它们能够高效处理多样的数据类型、格式和上下文大小,同时提供准确可靠的结果。解决方案如下:
-
定制化预处理:设计定制化的预处理技术,将非文本数据转换为能够被 LLM 高效处理的格式。例如,设计一个结构,将结构化的表格数据放置在 LLM 提示符中。
-
使用上下文限制扩展神经网络组件:实施先进的神经网络组件,如 LongLORA,需要对现有模型进行微调,以扩展上下文窗口大小,使 LLM 能够处理更多的信息量。然而,需要注意的是,这个选项可能不适用于外部 LLM 提供商,只有在考虑托管自己的 LLM 模型时才可行。
-
LLM 上下文优化:任何浪费空间或重复内容都会限制我们从中提取和生成答案的深度和广度。这里有三种可能的方法:
-
仅选择最相关和独特的信息包含在 LLM 的上下文窗口中。最大边缘相关性算法可用于从距离分数中找到一组既相关又独特的信息集。
-
考虑压缩和总结提供的信息,LLM 也可以执行这一任务,然后使用总结的信息作为主 LLM 提示中的上下文。
-
根据需求应用知识检索,而不是默认的方式。这种按需行为可以通过微调或上下文学习来实现,将 RAG 作为工具来教导 LLM 使用。
-
接下来,我们将通过知识和信息来解决挑战。
解决与知识和信息相关的挑战
解决输出和输入限制挑战对于增强 LLM 在解决各种数据模态和上下文大小的实际问题中的多功能性和有效性至关重要。解决方案如下:
-
实时数据集成:将 LLM 与实时数据源(如 API、数据库或网络服务)连接,可以帮助它们访问最新信息并提供更准确的响应。将来自知识库的相关信息融入其中,使用 RAG 方法是该解决方案的一部分。与通过自定义数据对模型进行微调相比,RAG 还可以帮助减少幻觉现象,前提是严格的提示词要求 LLM 不偏离提示中提供的上下文。
-
工具集成:通过集成现有的工具、API 和专门的算法来增强 LLM 架构,可以显著扩展其能力,使其能够处理独立模型难以完成或无法完成的任务。工具可以用于检索生成过程中所需的额外输入上下文,或者用于执行生成文本所要求的特定任务。例如,利用外部搜索引擎、特定领域 API 和计算库,提供准确的响应,解决复杂的数学问题,或回答与实时数据相关的查询。对于具有 API 访问权限的 LLM(如 GPT-3.5),可以通过有效的少量示例提示实现这一目标,而像Toolformer和 OpenAI 的WebGPT等高级模型则展示了将外部工具无缝集成到 LLM 参数记忆和框架中的潜力。WebGPT 可以通过检测其生成的 Bing 搜索引擎标识符来浏览互联网,并在继续生成文本前执行搜索。另一方面,Toolformer 是一种 LLM,能够自主选择并利用 API,集成如计算器、问答系统、搜索引擎、翻译器和日历等工具,以改进生成过程。这是将 LLM 转变为能够完成现实世界任务的智能体的关键功能。
-
[1,3,5,7,9,10,8,6,4,2],LLM 被鼓励平等关注文本的所有部分,从而减少遗漏中间部分有价值信息的可能性。 -
利用 LLM 上下文中的周围信息:该解决方案通过结合来自源文档的额外信息,增强了 LLM 的理解能力。扩大检索范围,包含周围的文本或元数据,帮助 LLM 生成更准确和全面的响应,确保其考虑更广泛的上下文。这种方法提高了 LLM 处理复杂问题并提供充分信息响应的能力,能有效解决文档搜索的使用案例问题。
-
使用 LLM 过滤掉无关的上下文:在进行生成任务之前,LLM 会被用来识别并移除任何无关的上下文。然后,经过精炼的上下文将用于生成响应。这种看似简单且合乎逻辑的方法在大多数案例中已证明其有效性,如论文
arxiv.org/abs/2311.11829v1中所介绍的那样。此外,这种技术的黑箱特性使其易于实现,从而促进了更直观、自然的 LLM 生成内容。 -
定期构建最新的知识库:为了解决单一索引失败的问题,定期维护和更新 LLM 的知识库是至关重要的。这确保了 LLM 能够跟上最新的发展,并提供准确的跨学科信息,最终提高其在解决现实世界问题时的可靠性和有效性。
-
将 RAG 视为 LLM 可以基于其生成动态使用的工具:这有助于解决无法在知识库的全局层面进行总结的问题。类似于死侍意识到自己是漫画书中的角色,我们需要检索过程能够意识到它正在从哪种类型的知识库中进行检索,并为总结任务配置一个特殊的处理程序。这里的一个额外好处是,允许 LLM 配置返回多少行相关文本,以便在需要时可以根据范围进行扩展和收缩。
-
多索引检索:为了解决单一索引失败的问题,可以采用多索引检索方法。该方案涉及将用户的查询分解,或者换句话说,拆分成多个组件,并从多个来源或知识索引中检索信息。这种多角度的搜索策略有助于收集更广泛和全面的信息,减少由于单一索引的局限性而忽视相关细节的可能性。假设用户询问某种稀有鸟类。使用单一索引可能获得的信息有限,而采用多索引检索方法,LLM 将执行以下操作:
-
将查询分解成多个组件(例如,栖息地、饮食和外貌)。
-
从多个来源检索数据(例如,鸟类学数据库、自然网站和社交媒体)。
-
汇总并综合数据,生成全面的响应。
-
-
设置有向无环图 (DAG) 工作流:设置 DAG 工作流涉及将一系列任务或过程组织成结构化的、非循环的顺序,以高效处理多个信息来源并扩展 LLM 的功能。在 LLM 的上下文中,可以手动设计一个 DAG 工作流,连接各种工具、API 和算法,同时解决与实时数据集成、工具集成和多索引检索相关的问题。我们来考虑一个用例,假设用户想要规划一次旅行,并需要了解目的地的各个方面,如天气、景点和当地美食。LLM 可以使用 DAG 工作流高效地解决这个复杂的查询。以下是 LLM 的 DAG 工作流示例:
-
将用户的查询分解为子查询或组件,具体来说是天气预报、热门景点和美食主题。
-
对于每个子查询,确定相关的工具、API 或数据源。对于天气预报,我们将从天气 API 获取数据。对于热门景点,我们将从旅游网站知识库中提取信息。对于当地美食,我们将从餐厅评价网站 API 获取数据。
-
在将每个事实作为 LLM 输入上下文的一部分之前,先对其进行总结。
-
使用用户查询和总结的事实执行 LLM 生成过程。
-
通过 API 将结果发布到网站上。
该 DAG 如 图 19.3 所示:
-

图 19.3 – 一个示例的 LLM DAG 工作流
通过设置手动 DAG 工作流,LLM 可以高效地处理来自多个来源的信息,利用外部工具和 API,提供准确可靠的回答,以应对各种现实世界中的问题。
该策略帮助 LLM 提供更准确、更详细的回答,即使在信息稀缺或无法在单一索引中轻易获取的情况下。这类问题通常被称为多跳问答。
接下来,我们将解决准确性和可靠性的问题。
解决准确性和可靠性的问题
确保 LLM 的准确性和可靠性对于建立对其能力的信任以及使其成为有效的解决问题工具至关重要。解决准确性和可靠性挑战的解决方案如下:
-
将 LLM 解决方案视为任何建模实验:将 LLM 与知识库配对,使用相关指标评估其性能,并收集见解,以根据深度学习生命周期迭代地调整其能力。这将帮助你选择一个至少能产生较少幻觉并帮助你理解其在特定应用中的有效性的模型。
-
微调检索嵌入模型:这不是仅仅依赖于预训练的嵌入模型或嵌入模型提供者。这样可以提高检索的准确性,从而提升 LLM 生成响应的质量。
-
提示工程:提示工程是制定有效且有针对性的提示,以引导语言模型的响应,从而提高其准确性、相关性和整体表现的过程。可以考虑实施以下技术:
-
思维链(CoT):该方法鼓励 LLM 生成逐步推理过程,从而在涉及算术、常识推理和其他问题解决场景的任务中产生更准确和结构化的响应。通过引导 LLM 经过一系列推理步骤,CoT 有助于减少诸如事实幻觉等问题,同时提高生成内容的整体质量和连贯性。
-
ReAct:这种方法是一个框架,它交替进行推理过程和任务特定的操作,使得 LLM 能够生成更可靠和真实的响应。通过结合动态推理和与外部来源的互动,ReAct 有效地解决了事实幻觉和错误传播等问题,从而提高了 LLM 的可解释性和可信度。
-
提示调优:提示调优是一种通过使用基于梯度的方法优化输入提示来细化 LLM 行为的技术,这使得更好地控制模型的响应,并提高在各种问题解决任务中的准确性和相关性。通过微调提示,用户可以有效地引导 LLM 生成更理想和更具上下文特定的输出。然而,这仅适用于你可以自己托管的 LLM。
-
-
依赖精心设计的提示:利用已发布的精心设计的提示,而不是自己设计一个。这是 Langchain 和 Auto-GPT 使用的一种技术。AutoGPT 是一个基于 GPT-4 的开源 Python 应用程序。它无需多次提示即可自动执行任务,使用 AI 代理访问网页并执行操作,所需的指导最小。与 ChatGPT 不同,AutoGPT 可以执行更大的任务,例如创建网站和制定营销策略,而不需要逐步的指令。它有多种应用,例如生成内容、设计徽标和开发聊天机器人。
-
拒绝采样(best-of-n)参考:使用拒绝采样技术通过从多个尝试中选择最佳响应来提高生成响应的质量。最佳响应可以通过选定的度量进行评估。
-
重新排名知识检索的相关性距离分数:知识检索属于推荐系统领域。常用的一种技术是实现适当的回归基础推荐模型来重新排名相关性距离分数。这有助于提供更准确且潜在的个性化的相关信息,伴随更多的上下文数据。大多数现实世界的大规模推荐产品,如 YouTube,都采用了这种技术。
-
迭代检索与生成:使用自问、自我激活 RAG、ITER-RETGEN 等技术生成临时响应,评估其质量,并通过检索的知识进行迭代优化。这种方法有助于减少幻觉现象,提高 LLM 生成内容的质量。
-
多智能体系统:开发由具有独特个性和背景的智能体组成的多智能体系统,以解决多样化的难题。这些智能体可以协作、共享信息,并提供更全面、更可靠的解决方案。一个例子是AutoAgents。AutoAgents 是一个创新框架,能够根据不同任务自适应地生成和协调多个专门的智能体,构建一个 AI 团队。该框架由两个阶段组成:草拟阶段和执行阶段。在草拟阶段,根据输入任务生成智能体团队和执行计划,而在执行阶段,通过智能体间的协作和反馈来优化计划,最终产生结果。AutoAgents 能够动态合成和协调多个专家智能体,为不同任务构建定制化的 AI 团队。关于开放式问答和创意写作任务的实验表明,与现有方法相比,AutoAgents 在效果上具有优势。AutoAgents 为解决复杂任务提供了新的视角,通过为不同任务分配不同的角色并促进团队合作。
接下来,我们将深入探讨应对运行时性能挑战的解决方案。
应对运行时性能挑战
运行时性能挑战是一个关键问题,它会显著影响语言模型的效率和效果。随着 LLM 的复杂性和规模不断增长,优化其运行时性能变得比以往任何时候都更加重要。解决这一问题的方案如下:
-
缓存输出:临时存储结果以避免重复计算信息,从而提高响应速度和性能。此方法在处理重复或相似查询时尤为有效。
-
GPU 和 GPU 推理加速器:这仅适用于您自己托管的 LLM。LLM 需要依赖这些组件才能在合理的时间内运行。在第十五章,将深度学习模型 部署到生产环境 中,这些内容已有更详细的介绍。
-
scann算法和faissIVFPQFS 算法在索引构建时间、索引大小、检索召回率和检索运行时之间提供了良好的平衡。然而,仅在大规模知识库中才需要使用近似 KNN 算法,因为小型知识库的检索速度已经非常快,通常小于 1 秒。通常,适合的数据维度范围在三位数的向量列大小和七位数的向量行大小之间。
解决伦理影响和社会影响的挑战
解决 LLM 解决方案的伦理影响和社会影响对于确保其在各种应用中的负责任和可持续部署至关重要。通过考虑 LLM 生成内容的伦理和社会后果,开发者可以创建尊重用户价值观、遵循法律指南并对社会产生积极贡献的模型。
解决这些挑战的策略如下:
-
偏见和公平性缓解:在 第十三章中介绍的方法的背景下,考虑以下策略,探索偏见 与公平性:
-
数据收集与准备:确保用于微调 LLM 模型的数据集多样且具有代表性。平衡数据中的敏感属性,并消除或控制可能由这些属性引发的偏见。此外,你可以指示 LLM 在自然语言的输入上下文中明确不传播偏见。更好的是,赋予用户定义偏好、价值观和伦理指导方针的权力,使 LLM 能够生成符合个别用户需求和价值观的内容。
-
微调过程中的偏见缓解:在微调过程中实施对抗性数据增强、对抗训练或重采样等技术,以减少偏见特征的影响并提高公平性。
-
后处理:使用平衡后处理等技术修改 LLM 生成的内容,确保输出结果的公平性。这可以应用于使用像 OpenAI GPT-4 或微调的开源模型等 LLM 提供商时。
-
监控与评估:持续监控 LLM 生成的内容,使用偏见和公平性指标识别潜在偏见,并根据需要调整模型,确保符合伦理指南和公平性要求。
-
-
隐私保护技术:采用隐私保护方法,如差分隐私、联邦学习和同态加密,保护训练数据和生成内容中的敏感信息。实施政策和指导方针,以防止在 LLM 生成内容中无意泄露个人可识别信息(PII)。
-
事实核查和可信度评估:将事实核查和可信度评估机制整合到 LLM 解决方案中,以减少生成误导性或虚假信息的风险。这可以通过将 LLM 与外部知识源(如知识数据库)集成,验证生成内容的准确性来实现。你还可以指示 LLM 保持谦逊,在无法进行事实验证时不返回任何声明。
-
内容审查和防护措施:实施内容审查技术,如关键词过滤、基于机器学习的分类器以及人工审核流程,防止生成有害或冒犯性内容。建立防护措施,如有毒性阈值或伦理指南,以确保 LLM 生成的内容符合社区标准和法律规定。
-
透明度和可解释性:开发方法,增强 LLM 生成内容的透明度和可解释性,例如提供推理痕迹、显著性图或反事实解释。在第十一章《解释神经网络预测》以及第十二章《解释神经网络》中介绍的概念可以应用于 LLM。
通过实施这些策略,开发者可以创建符合用户价值观并遵守法律指南的 LLM 解决方案,同时也能对社会做出积极贡献。解决 LLM 的伦理影响和社会影响是建立对技术信任的关键一步,确保其在各类应用中的负责任和可持续部署。
在全面理解了与 LLM 相关的挑战及其潜在解决方案之后,我们现在可以将注意力转向解决 LLM 解决方案在各组织和行业中推广的重大挑战。
解决 LLM 解决方案采纳的重大挑战
实现 LLM 解决方案全部潜力的一个重大挑战在于其在各个组织和行业中的采纳。与任何机器学习或深度学习解决方案的采纳类似,推动 LLM 采纳的关键因素是信任。对技术能力的信任、其在解决特定使用案例中的有效性以及其交付实际成果的能力是广泛采纳的必要条件。
为了克服这一挑战,至关重要的是系统性地教育各组织了解 LLM 的强大能力、其多样的应用及如何根据特定需求量身定制 LLM。这包括通过真实的成功案例展示 LLM 的优势,提供实施 LLM 解决方案的实际指导,并为各组织在将 LLM 集成到工作流中的复杂性提供支持。
建立对 LLM 解决方案的信心需要全面评估其性能,解决本章前面讨论的挑战,并确保解决方案能够实现预期目标。通过实施包括定量指标和定性人工评估在内的全面评估方法,组织可以更好地评估 LLM 解决方案的性能、准确性和整体质量。这些评估应该是迭代进行的,以便持续改进 LLM 解决方案。
此外,解决本章中提到的挑战,如输出和输入限制、与知识和信息相关的挑战、准确性和可靠性问题以及运行时性能挑战,对于建立对 LLM 解决方案的信心至关重要。通过利用本章讨论的策略和技术,组织可以优化 LLM 的性能,确保其在各种应用中的有效性。
建立对 LLM 解决方案的信心的另一个关键方面是与利益相关者进行有效的沟通与协作。这包括共享评估结果、讨论 LLM 的优点和潜在局限性,以及解决利益相关者对 LLM 解决方案采用可能存在的任何疑虑。
总之,成功采用 LLM 解决方案需要结合严格的评估、解决挑战和与利益相关者的有效沟通。通过以与任何机器学习或深度学习解决方案相同的关注度对待 LLM 解决方案的采用,组织能够建立对 LLM 能力和性能的信心,释放其在广泛现实应用中的全部潜力。至此,我们已经详细探讨了困扰 LLM 的挑战及其解决方案。
在接下来的部分,我们将深入探讨如何利用 LLM 构建自主代理,这可以显著扩展和提升我们的解决问题能力。
利用 LLM 构建自主代理
LLM 可以在一个有前景的领域中得到应用——开发能够高效解决复杂问题并与环境互动的自主代理。本节将重点讨论如何利用 LLM 构建这样的代理,并讨论其有效性的关键因素。
自主代理是由 AI 驱动的实体,能够独立执行任务、做出决策并与环境互动。通过将 LLM 融入这些代理,开发者可以创建出多功能且具有适应性的系统,能够应对各种挑战。以下是 LLM 驱动的自主代理的一些关键组成部分:
-
规划与决策:LLM 可以用来生成引导代理行动的计划和策略,考虑到上下文和目标。
-
观察和从环境中学习:LLM 可以训练观察并解读环境,从过去的经验中学习,并相应地调整其行为。
-
协作问题解决:可以开发多代理系统,每个代理都有独特的个性和背景。这些代理可以协作、共享信息,并提供更全面、更可靠的解决方案。
-
自我完善:自主代理可以利用 LLM 分析其表现,识别需要改进的领域,并随着时间推移完善其策略和行为。
代理包含了本章识别 LLM 解决方案中的挑战部分中提出的挑战解决方案的各个部分。此外,这里介绍的解决方案可以组合起来,以扩展整体架构化 LLM 解决方案所能覆盖的问题范围。本章早些时候介绍的已发布代理方法示例包括 WebGPT、Toolformer、Auto-GPT 和 AutoAgents。利用 LLM 的自主代理是实现强大 LLM 解决方案的关键。通过将 LLM 的优势与代理的适应性和决策能力相结合,开发者可以创造出革命性的系统,彻底改变各个领域和行业。
通过全面了解 LLM 解决方案及其潜在应用,让我们探索一些能够有效应用这些解决方案的具体案例。
探索 LLM 解决方案的应用案例
在这一部分中,我们将探索一些令人着迷的现实世界应用案例,展示 LLM 解决方案如何真正发挥作用。这将让你感受到 LLM 解决方案的革命性。以下是这些应用案例:
-
旅行行程规划器:LLM 可以用于开发先进的旅行行程规划器,根据用户的偏好和约束生成个性化的旅行计划。通过将 LLM 与旅行 API(如航班、酒店和景点数据库)以及实时数据源(如天气和交通信息)集成,这些规划器能够提供针对个别旅行者需求的上下文感知推荐。值得注意的是,Booking.com 和 Expedia 等公司已将此功能整合到他们的产品中,Agoda 也宣布将会进行相关工作。
-
智能辅导系统:LLM 可以用于开发智能辅导系统,为学生提供个性化的学习体验。通过将 LLM 与教育内容、评估工具和学习者数据集成,这些系统能够生成有针对性的学习材料、提供实时反馈,并根据个别学习需求进行适应。这为学生提供了更高效、更有趣的学习体验。值得注意的是,Duolingo 是一家在其游戏化语言学习产品中实现了此类解决方案的公司。
-
自动化电子邮件回复:LLM 解决方案可以用于开发自动化电子邮件回复系统,处理各种类型的咨询,如客户支持、销售询问或一般信息请求。通过将 LLMs 与电子邮件 API、CRM 系统和相关知识库集成,电子邮件回复可以个性化、准确并具有上下文相关性。这有助于企业简化客户沟通并提供高效的支持。值得注意的是,Nanonets AI 公司在其产品中实现了这一解决方案。
-
代码生成:LLMs 可以根据用户输入或特定需求生成代码片段、算法或整个软件程序。像 GitHub Copilot 这样的解决方案利用 LLMs 帮助开发者编写代码,建议相关的代码片段,并根据上下文完成代码的部分内容。通过将 LLMs 与代码仓库、编程语言 API 和特定领域的知识库结合,代码生成可以根据特定的编程语言、框架和使用场景量身定制,从而提升开发者的生产力。
-
客户支持聊天机器人:LLM 解决方案可以用于开发先进的、具有上下文感知的聊天机器人,更有效地处理客户咨询和支持请求。通过将 LLMs 与客户关系管理(CRM)系统和知识库结合,聊天机器人能够为客户查询提供个性化且准确的回答。这有助于企业提升客户支持服务、减少响应时间,并提高客户满意度。像 forethought.ai、Ada 和 EBI.AI 等公司在其产品中提供了类似的解决方案。
-
医疗诊断和治疗建议:大语言模型(LLMs)可以用于开发先进的诊断工具,通过分析患者的症状、病史和相关医学文献,建议潜在的诊断和治疗方案。通过将 LLMs 与电子健康记录(EHR)系统、医学数据库以及特定领域的知识库结合,这些工具可以帮助医疗专业人员做出更为明智的决策,改善患者的治疗效果。值得注意的是,三星公司 Harman 已经实施并提供了这样的解决方案,作为其服务的一部分。
-
个人财务管理:LLMs 可以用于开发智能的个人财务管理应用,根据用户特定的财务目标和风险承受能力,提供量身定制的财务建议、预算建议和投资推荐。通过将 LLMs 与银行 API、股市数据和财务知识库集成,这些应用能够为用户提供具有上下文感知的财务规划和指导。虽然不完全是一个服务或产品,但彭博公司开发了 BloombergGPT,这是一款专为金融领域设计的 50 亿参数的大型语言模型,展示了 LLMs 在金融领域的潜力。
-
创意内容生成:LLMs 可用于根据用户输入、偏好或灵感生成创意内容,如故事、诗歌或音乐。通过将 LLM 与文学作品数据库、音乐库和创作技巧与风格的知识库集成,这些应用程序可以生成独特且吸引人的内容,迎合个人艺术品味和需求。值得注意的是,Jasper 构建了一个平台来应对这一用例。
-
法律文档分析与草拟:LLMs 可用于开发先进的法律文档分析与草拟工具,帮助法律专业人士审查合同、识别潜在问题,并根据特定需求生成法律文档。通过将 LLM 与法律数据库、合同模板及领域特定知识库集成,这些工具可以帮助简化法律工作,并提高法律行业的工作效率。值得注意的是,netdocuments 通过其产品实现了这一用例。
-
智能家居自动化:LLMs 可用于开发智能家居自动化系统,这些系统可以理解自然语言命令,并适应用户的偏好和日常作息。通过将 LLM 与智能家居设备、API 以及用户行为数据集成,这些系统可以提供更加直观和个性化的家居自动化体验,使用户能够轻松便捷地控制家居环境。Amazon Alexa 就是这一用例的典型代表。
在这些用例中,将 LLM 与相关工具、API 和数据源集成,可以确保生成的内容、推荐和响应是准确的、上下文相关的,并且根据特定需求量身定制,从而提升用户体验并在各个领域提供有价值的支持。
摘要
在本章中,我们探讨了大语言模型(LLMs)及其解决现实问题和在各种应用中创造价值的潜力。我们讨论了架构 LLM 解决方案的关键方面,例如处理知识、与实时数据和工具交互、评估 LLM 解决方案、识别并解决挑战,以及利用 LLM 构建自主代理。我们还强调了检索增强语言模型在提供上下文相关信息方面的重要性,并考察了多种技术和库,以改善 LLM 解决方案。
我们还讨论了 LLM 的局限性,例如输出和输入的限制、知识和信息相关的挑战、准确性和可靠性问题、运行时性能问题、伦理影响和社会影响,以及 LLM 解决方案采用的总体挑战。为了解决这些局限性,我们提出了多种互补策略,如实时数据集成、工具集成、提示工程、拒绝采样、多代理系统、运行时优化技术、偏见和公平性缓解、内容审查,以及实现 LLM 透明性和可解释性。最后,我们讨论了利用 LLM 构建自主代理,这可以显著扩展和提升在各种应用中的问题解决能力。
通过理解 LLM 解决方案的复杂性,并应用本章讨论的策略和技术,组织和个人可以充分发挥 LLM 的潜力,推动创新并改善各类应用中的成果。
通过阅读《深度学习架构手册》,你获得了对深度学习生命周期各个阶段的宝贵洞察,探索了从规划、数据准备到模型部署和治理的关键方面。在完成这段启发性的学习旅程后,你现在已经具备了设计、开发和部署有效深度学习解决方案的知识和技能。为了在这一坚实的基础上继续进步,考虑采取以下下一步行动:
-
将你新学到的知识应用到实际项目中,无论是在你的专业领域,还是通过开源贡献,获取实践经验并加深理解。
-
通过参加会议、关注有影响力的研究人员以及阅读研究论文,保持对深度学习领域最新研究、趋势和突破的了解。
-
探索深度学习中你感兴趣的专业领域,如强化学习、生成对抗网络或少样本学习,进一步拓展你的专业知识。
-
与其他深度学习爱好者和专业人士合作,加入社区、讨论论坛和社交媒体群组,交流思想、分享经验、互相学习。
-
考虑攻读深度学习或相关领域的进阶课程、认证,甚至是学位,以提升你的教育水平和资质。
拥抱未来的挑战与胜利,因为通过掌握构建深度学习模型和复杂深度学习架构的技能,深刻理解偏见和公平性,以及能够监控和维持模型性能,你已经为释放深度学习的全部潜力并推动广泛应用中的创新做好了充分准备。祝你在深度学习的世界中继续取得成功和成长!
深入阅读




浙公网安备 33010602011771号