英文原文:https://fastai.github.io/fastbook2e/book2.html
2 从模型到产品
我们在第一章看到的六行代码只是深度学习在实践中使用过程的一小部分。 在本章中,我们将使用计算机视觉示例来了解创建深度学习应用程序的端到端过程。 更具体地说,我们将构建一个熊分类器! 在此过程中,我们将讨论深度学习的功能和限制,探索如何创建数据集,研究在实践中使用深度学习时可能出现的问题等等。 许多关键点同样适用于其他深度学习问题,例如第 1 章中的问题。如果您解决的问题在关键方面与我们的示例问题类似,我们希望您能够用很少的代码快速获得出色的结果。
让我们从如何构建问题开始。
2.1 深度学习的实践
我们已经看到深度学习可以用很少的代码快速解决许多具有挑战性的问题。 作为初学者,有一些问题与我们的示例问题非常相似,您可以很快获得非常有用的结果。 然而,深度学习并不神奇! 同样的 6 行代码并不能解决当今任何人都能想到的所有问题。
低估深度学习的限制和高估深度学习的能力可能会导致令人沮丧的糟糕结果,至少在您获得一些经验并能够解决出现的问题之前是这样。 相反,高估深度学习的限制并低估深度学习的能力可能意味着你不会尝试解决可解决的问题,因为你说服自己放弃了它。
我们经常与那些低估深度学习的限制和能力的人交谈。 这两者都可能是问题:低估能力意味着您甚至可能不会尝试可能非常有益的事情,而低估限制可能意味着您无法考虑重要问题并对重要问题做出反应。
最好的办法就是保持开放的心态。 如果您对深度学习可能用比您预期更少的数据或复杂性解决部分问题的可能性持开放态度,那么您可以设计一个流程,在其中您可以在工作时找到与您的特定问题相关的特定功能和约束 通过这个过程。 这并不意味着做出任何冒险的赌注——我们将向您展示如何逐步推出模型,以便它们不会产生重大风险,甚至可以在将它们投入生产之前对其进行回溯测试。
开始你的项目
那么你应该从哪里开始你的深度学习之旅呢? 最重要的是确保您有一些项目可以开展——只有通过开展自己的项目,您才能获得构建和使用模型的真正经验。 选择项目时,最重要的考虑因素是数据可用性。 无论您做的项目是为了自己学习还是为了在组织中实际应用,您都希望有一个可以快速入门的项目。 我们看到许多学生、研究人员和行业从业者在试图找到完美的数据集时浪费了数月或数年的时间。 目标不是找到“完美”的数据集或项目,而只是从那里开始并迭代。
如果您采用这种方法,那么您将进行第三次学习和改进,而完美主义者仍处于计划阶段!
我们还建议您在项目中从头到尾进行迭代; 也就是说,不要花几个月的时间微调你的模型,或者完善完美的 GUI,或者标记完美的数据集……相反,在合理的时间内尽你所能完成每一步,一直到最后。 例如,如果您的最终目标是在移动电话上运行的应用程序,那么这应该是您每次迭代后所拥有的。 但也许在早期迭代中,您会采取一些捷径,例如在远程服务器上进行所有处理,并使用简单的响应式 Web 应用程序。 通过从头到尾完成项目,您将看到最棘手的部分在哪里,以及哪些部分对最终结果影响最大。
当您学习本书时,我们建议您通过运行和调整我们提供的笔记本来完成大量小实验,同时逐步开发自己的项目。 这样,您将在我们讨论时获得我们正在解释的所有工具和技术的经验。
s:为了充分利用本书,请花时间在每一章之间进行实验,无论是在您自己的项目中还是通过探索我们提供的笔记本。 然后尝试在新数据集上从头开始重写这些笔记本。 只有通过大量练习(和失败),您才能直观地了解如何训练模型。
通过使用端到端迭代方法,您还可以更好地了解真正需要多少数据。 例如,您可能会发现只能轻松获得 200 个带标签的数据项,并且在尝试之前您无法真正知道这是否足以获得应用程序在实践中良好运行所需的性能。
在组织环境中,您将能够通过向同事展示真实的工作原型来向他们展示您的想法确实可行。 我们反复观察到,这是获得项目良好组织支持的秘诀。
由于开始一个已经拥有可用数据的项目是最容易的,这意味着开始一个与您已经在做的事情相关的项目可能是最容易的,因为您已经拥有有关您正在做的事情的数据。 例如,如果您从事音乐行业,您可能可以访问许多录音。 如果您是一名放射科医生,您可能会接触到大量的医学图像。 如果您对野生动物保护感兴趣,您可能会获得大量野生动物的图像。
有时,你必须发挥一点创意。 也许您可以找到一些与您感兴趣的领域相关的以前的机器学习项目,例如 Kaggle 竞赛。 有时,你必须妥协。 也许您无法找到您想要的精确项目所需的准确数据; 但您也许能够从类似的领域找到一些东西,或者以不同的方式进行测量,从而解决略有不同的问题。 从事这些类型的类似项目仍然可以让您很好地理解整个过程,并可以帮助您识别其他快捷方式、数据源等。
特别是当你刚刚开始深度学习时,扩展到非常不同的领域、深度学习以前没有应用过的地方并不是一个好主意。 这是因为,如果你的模型一开始不起作用,你将不知道这是因为你犯了错误,还是你试图解决的问题根本无法通过深度学习解决。 而且您将不知道到哪里寻求帮助。 因此,最好首先从一些地方开始,您可以在网上找到一个示例,其中有人已经取得了良好的结果,其结果至少与您想要实现的目标有些相似,或者您可以将数据转换为某种格式与其他人之前使用过的类似(例如从您的数据创建图像)。 我们先看一下深度学习的现状,这样你就知道深度学习现在擅长哪些方面。
深度学习的现状
让我们首先考虑深度学习是否能有效解决您想要解决的问题。 本节总结了 2020 年初深度学习的状况。然而,事情发展得非常快,当您阅读本文时,其中一些限制可能已不复存在。 我们将尽力使本书的网站保持最新状态; 此外,谷歌搜索“人工智能现在能做什么”可能会提供当前信息。
计算机视觉
在许多领域,深度学习尚未用于分析图像,但那些尝试过的领域几乎普遍表明,计算机至少可以像人类一样识别图像中的项目,甚至是经过专门训练的人 ,例如放射科医生。 这称为物体识别。 深度学习还擅长识别图像中的对象所在的位置,并且可以突出显示它们的位置并命名每个找到的对象。 这称为对象检测(我们在 <> 中看到了它的一个变体,其中每个像素根据其所属的对象类型进行分类 - 这称为分割)。 深度学习算法通常不擅长识别结构或风格与用于训练模型的图像显着不同的图像。 例如,如果训练数据中没有黑白图像,则模型在黑白图像上的表现可能会很差。 同样,如果训练数据不包含手绘图像,那么模型在手绘图像上的表现可能会很差。 没有通用的方法来检查训练集中缺少哪些类型的图像,但我们将在本章中展示一些方法,以尝试识别模型在生产中使用时数据中何时出现意外的图像类型(这是称为检查域外数据)。
物体检测系统面临的一项主要挑战是图像标记可能缓慢且昂贵。 目前在工具方面做了很多工作,试图使这种标记更快、更容易,并需要更少的手工标签来训练准确的对象检测模型。 一种特别有用的方法是综合生成输入图像的变化,例如通过旋转它们或改变它们的亮度和对比度; 这称为数据增强,也适用于文本和其他类型的模型。 我们将在本章中详细讨论它。
需要考虑的另一点是,尽管您的问题可能看起来不像计算机视觉问题,但只需一点想象力就可以将其变成计算机视觉问题。 例如,如果您要尝试分类的是声音,您可以尝试将声音转换为其声学波形的图像,然后在这些图像上训练模型。
文本(自然语言处理)
计算机非常擅长根据垃圾邮件或非垃圾邮件、情绪(例如评论是正面还是负面)、作者、源网站等类别对短文档和长文档进行分类。 我们不知道在这一领域进行过任何严格的工作来将它们与人类进行比较,但有趣的是,在我们看来,深度学习的表现与人类在这些任务上的表现相似。 深度学习还非常擅长生成适合上下文的文本,例如对社交媒体帖子的回复,以及模仿特定作者的风格。 它也擅长让这些内容对人类有吸引力——事实上,甚至比人类生成的文本更有吸引力。 然而,深度学习目前并不擅长生成正确的响应! 例如,我们目前没有可靠的方法将医学信息知识库与深度学习模型相结合来生成医学上正确的自然语言响应。 这是非常危险的,因为很容易创建对外行来说似乎很有吸引力但实际上完全不正确的内容。
另一个担忧是,社交媒体上适合情境、极具说服力的回应可能会被大规模使用——比以前见过的任何巨魔农场都要大数千倍——来传播虚假信息、制造骚乱和鼓励冲突。 根据经验,文本生成模型在技术上总是领先于识别自动生成文本的模型。 例如,可以使用可以识别人工生成内容的模型来实际改进创建该内容的生成器,直到分类模型不再能够完成其任务。
尽管存在这些问题,深度学习在 NLP 中有许多应用:它可以用来将文本从一种语言翻译成另一种语言,将长文档总结成可以更快消化的内容,找到所有提到的感兴趣概念等等。 不幸的是,翻译或摘要很可能包含完全错误的信息! 然而,性能已经足够好,以至于很多人都在使用这些系统——例如,谷歌的在线翻译系统(以及我们知道的所有其他在线服务)都是基于深度学习的。
结合文本和图像
一般来说,深度学习将文本和图像组合成单个模型的能力远远超出大多数人的直观预期。 例如,深度学习模型可以在输入图像上进行训练,并输出英文字幕,并且可以学习为新图像自动生成非常合适的字幕! 但我们再次收到与上一节中讨论的相同警告:无法保证这些标题实际上是正确的。
由于这个严重的问题,我们通常建议不要将深度学习用作完全自动化的过程,而是将其用作模型和人类用户密切交互的过程的一部分。 与完全手动方法相比,这可能会使人类的生产力提高几个数量级,并且实际上会比单独使用人类产生更准确的流程。 例如,自动系统可用于直接通过 CT 扫描识别潜在的中风受害者,并发送高优先级警报以快速查看这些扫描。 治疗中风的时间只有三个小时,因此这种快速反馈循环可以挽救生命。 但与此同时,所有扫描结果都可以继续以通常的方式发送给放射科医生,因此人力投入不会减少。 其他深度学习模型可以自动测量扫描中看到的项目,并将这些测量结果插入到报告中,警告放射科医生他们可能错过的发现,并告诉他们其他可能相关的病例。
表格数据
在分析时间序列和表格数据方面,深度学习最近取得了长足的进步。 然而,深度学习通常用作多种类型模型集成的一部分。 如果您已经拥有一个使用随机森林或梯度增强机(您很快就会了解的流行表格建模工具)的系统,那么切换到或添加深度学习可能不会带来任何显着的改进。 深度学习确实大大增加了您可以包含的列的种类,例如,包含自然语言的列(书名、评论等)和高基数分类列(即包含大量离散选择的列, 例如邮政编码或产品 ID)。 不利的一面是,深度学习模型的训练时间通常比随机森林或梯度增强机需要更长的时间,尽管由于 RAPIDS 等库的出现,这种情况正在发生变化,它为整个建模流程提供了 GPU 加速。 我们在 <> 中详细介绍了所有这些方法的优缺点。
推荐系统
推荐系统实际上只是一种特殊类型的表格数据。 特别是,它们通常有一个代表用户的高基数分类变量,另一个代表产品(或类似的东西)。 像亚马逊这样的公司将客户的每一次购买表示为一个巨大的稀疏矩阵,其中客户为行,产品为列。 一旦获得这种格式的数据,数据科学家就会应用某种形式的协同过滤来填充矩阵。 例如,如果客户A购买产品1和10,客户B购买产品1、2、4和10,引擎会建议A购买2和4。因为深度学习模型擅长处理高基数分类变量 ,他们非常擅长处理推荐系统。 就像表格数据一样,当将这些变量与其他类型的数据(例如自然语言或图像)组合时,它们尤其会发挥作用。 他们还可以很好地将所有这些类型的信息与以表格表示的附加元数据(例如用户信息、以前的交易等)结合起来。
然而,几乎所有机器学习方法都有一个缺点,即它们只告诉您特定用户可能喜欢哪些产品,而不是告诉您哪些推荐对用户有帮助。 对用户可能喜欢的产品的多种推荐可能根本没有帮助——例如,如果用户已经熟悉该产品,或者它们只是他们已经购买的产品的不同包装(例如一套盒装产品) 小说,当他们已经拥有该集中的每个项目时)。 杰里米喜欢阅读特里·普拉切特的书,有一段时间亚马逊只向他推荐特里·普拉切特的书(参见<>),这确实没有帮助,因为他已经知道这些书了!
其他数据类型
通常,您会发现特定于领域的数据类型非常适合现有类别。 例如,蛋白质链看起来很像自然语言文档,因为它们是离散标记的长序列,在整个序列中具有复杂的关系和含义。 事实上,事实证明,使用 NLP 深度学习方法是当前许多类型蛋白质分析的最先进方法。 另一个例子,声音可以表示为频谱图,频谱图可以被视为图像; 事实证明,标准的图像深度学习方法在频谱图上效果非常好。
传动系统方法
有许多准确的模型对任何人都没有用,也有许多不准确的模型非常有用。 为了确保您的建模工作在实践中有用,您需要考虑如何使用您的工作。 2012 年,Jeremy 与 Margit Zwemer 和 Mike Loukides 一起提出了一种称为传动系统方法的方法来思考这个问题。
图中所示的传动系统方法在“设计大数据产品”中进行了详细描述。 基本思想是首先考虑您的目标,然后考虑您可以采取哪些行动来实现该目标以及您拥有(或可以获得)哪些数据可以提供帮助,然后构建一个模型,您可以使用该模型来确定最佳方案 为实现您的目标而获得最佳结果而采取的行动。
考虑自动驾驶汽车中的模型:您希望帮助汽车在没有人工干预的情况下安全地从 A 点行驶到 B 点。 出色的预测建模是解决方案的重要组成部分,但它并不是独立存在的。 随着产品变得更加复杂,它消失在管道中。 使用自动驾驶汽车的人完全不知道数百个(如果不是数千个)模型和使其运行的数 PB 数据。 但随着数据科学家构建日益复杂的产品,他们需要一种系统的设计方法。
我们使用数据不仅是为了生成更多数据(以预测的形式),而且是为了产生可操作的结果。 这就是传动系统方法的目标。 首先定义一个明确的目标。 例如,谷歌在创建他们的第一个搜索引擎时,考虑了“用户输入搜索查询的主要目标是什么?” 这引导他们实现了目标,即“显示最相关的搜索结果”。 下一步是考虑可以使用哪些杠杆(即可以采取哪些行动)来更好地实现该目标。 就谷歌而言,这就是搜索结果的排名。 第三步是考虑他们需要哪些新数据来产生这样的排名; 他们意识到,关于哪些页面链接到哪些其他页面的隐含信息可以用于此目的。 只有在完成前三个步骤之后,我们才开始考虑构建预测模型。 我们的目标和可用杠杆、我们已经拥有哪些数据以及我们需要收集哪些额外数据,决定了我们可以构建的模型。 该模型将杠杆和任何不可控变量作为输入; 可以组合模型的输出来预测我们目标的最终状态。
让我们考虑另一个例子:推荐系统。 推荐引擎的目标是通过向客户推荐没有推荐就不会购买的商品,让客户感到惊讶和高兴,从而推动额外销售。 杠杆是推荐的排名。 必须收集新数据以生成可带来新销售的建议。 这将需要进行许多随机实验,以便为广泛的客户收集有关广泛建议的数据。 很少有组织采取这一步骤; 但如果没有它,您就没有根据您的真实目标(更多销售额!)实际优化推荐所需的信息。
最后,您可以构建两个购买概率模型,以看到或不看到推荐为条件。 这两个概率之间的差异是针对给客户的给定推荐的效用函数。 如果算法推荐一本客户已经拒绝的熟悉的书(两个组件都很小),或者即使没有推荐他们也会购买的一本书(两个组件都很大并且相互抵消),那么它会很低。
正如您所看到的,在实践中,模型的实际实施通常需要的不仅仅是训练模型! 您经常需要进行实验来收集更多数据,并考虑如何将模型合并到您正在开发的整个系统中。 说到数据,现在让我们关注如何为您的项目查找数据。
收集数据
对于许多类型的项目,您也许能够在线找到所需的所有数据。 我们将在本章中完成的项目是熊探测器。 它将区分三种类型的熊:灰熊、黑熊和泰迪熊。 互联网上有很多我们可以使用的每种熊的图像。 我们只需要一种找到它们并下载它们的方法。 我们提供了一个可用于此目的的工具,因此您可以按照本章的内容,为您感兴趣的任何类型的对象创建自己的图像识别应用程序。在 fast.ai 课程中,成千上万的学生已经 在课程论坛上展示了他们的作品,展示了从特立尼达的蜂鸟品种到巴拿马的巴士类型等各种内容,一名学生甚至创建了一个应用程序,可以帮助他的未婚妻在圣诞节假期期间认出他的 16 个表兄弟姐妹!
在撰写本文时,Bing 图像搜索是我们所知的查找和下载图像的最佳选项。 每月最多可免费进行 1,000 个查询,每个查询最多可下载 150 张图像。 然而,在我们撰写本文和您阅读本书之间可能会出现更好的内容,因此请务必查看本书的网站以获取我们当前的推荐。
重要提示:与最新服务保持联系:可用于创建数据集的服务一直在变化,其功能、界面和定价也会定期变化。 在本节中,我们将展示如何使用本书撰写时可用的 Bing 图像搜索 API。 我们将在本书的网站上提供更多选项和更多最新信息,因此请务必立即查看该网站,以获取有关如何从网络下载图像以创建深度学习数据集的最新信息。
clean
要使用 Bing 图像搜索下载图像,请在 Microsoft Azure 上注册免费帐户。 您将获得一个密钥,您可以将其复制并输入到单元格中,如下所示(将“XXX”替换为您的密钥并执行它):
key = os.environ.get('AZURE_SEARCH_KEY', 'XXX')
或者,如果您熟悉命令行,则可以在终端中使用以下命令进行设置:
export AZURE_SEARCH_KEY=your_key_here
然后重新启动 Jupyter Notebook,并使用上面的行而不对其进行编辑。
设置密钥后,即可使用 search_images_bing。 此功能由在线笔记本附带的小型 utils 类提供。 如果您不确定函数的定义位置,只需在笔记本中键入即可查找:
search_images_bing
<function fastbook.search_images_bing(key, term, min_sz=128, max_images=150)>
results = search_images_bing(key, 'grizzly bear')
ims = results.attrgot('contentUrl')
len(ims)
我们已成功下载了 150 只灰熊的 URL(或者至少是 Bing 图像搜索为该搜索词找到的图像)。
注意:没有办法确定这样的搜索会找到什么图像。 结果可能会随着时间的推移而改变。 我们听说过至少一个社区成员在搜索结果中发现一些令人不快的死熊照片的案例。 您将收到网络搜索引擎找到的任何图像。 如果您在工作中或与孩子一起运行此程序,请在显示下载的图像之前务必小心。
我们来看一个:
#hide
ims = ['http://3.bp.blogspot.com/-S1scRCkI3vY/UHzV2kucsPI/AAAAAAAAA-k/YQ5UzHEm9Ss/s1600/Grizzly%2BBear%2BWildlife.jpg']
dest = 'images/grizzly.jpg'
download_url(ims[0], dest)
im = Image.open(dest)
im.to_thumb(128,128)
这看起来效果很好,所以让我们使用 fastai 的 download_images 来下载每个搜索词的所有 URL。 我们将把每个放在一个单独的文件夹中:
bear_types = 'grizzly','black','teddy'
path = Path('bears')
if not path.exists():
path.mkdir()
for o in bear_types:
dest = (path/o)
dest.mkdir(exist_ok=True)
results = search_images_bing(key, f'{o} bear')
download_images(dest, urls=results.attrgot('contentUrl'))
正如我们所期望的,我们的文件夹包含图像文件:
fns = get_image_files(path)
fns
j:我就是喜欢在 Jupyter 笔记本中工作! 逐步构建我想要的东西并检查每一步的工作是非常容易的。 我犯了很多错误,所以这对我来说真的很有帮助…
通常,当我们从互联网下载文件时,有一些文件已损坏。 让我们检查:
failed = verify_images(fns)
failed
(#11) [Path(‘bears/black/00000147.jpg’),Path(‘bears/black/00000057.jpg’),Path(‘bears/black/00000140.jpg’),Path(‘bears/black/00000129.jpg’),Path(‘bears/teddy/00000006.jpg’),Path(‘bears/teddy/00000048.jpg’),Path(‘bears/teddy/00000076.jpg’),Path(‘bears/teddy/00000125.jpg’),Path(‘bears/teddy/00000090.jpg’),Path(‘bears/teddy/00000075.jpg’)…]
要删除所有失败的图像,您可以对每个图像使用 unlink。 请注意,与大多数返回集合的 fastai 函数一样,verify_images 返回 L 类型的对象,其中包括 map 方法。 这对集合的每个元素调用传递的函数:
failed.map(Path.unlink);
侧边栏:在 Jupyter Notebooks 中获取帮助
Jupyter Notebook 非常适合进行实验并立即查看每个函数的结果,但也有很多功能可以帮助您了解如何使用不同的函数,甚至直接查看其源代码。 例如,如果您在单元格中键入:
??verify_images
将弹出一个窗口:
Signature: verify_images(fns)
Source:
def verify_images(fns):
"Find images in `fns` that can't be opened"
return L(fns[i] for i,o in
enumerate(parallel(verify_image, fns)) if not o)
File: ~/git/fastai/fastai/vision/utils.py
Type: function
这告诉我们函数接受什么参数(fns),然后向我们显示源代码和它来自的文件。 查看该源代码,我们可以看到它并行应用了 verify_image 函数,并且只保留该函数结果为 False 的图像文件,这与 doc 字符串一致:它在 fns 中查找不能的图像被打开。
以下是 Jupyter 笔记本中非常有用的其他一些功能:
- 在任何时候,如果您不记得函数或参数名称的确切拼写,都可以按 Tab 键获取自动完成建议。
- 当在函数的括号内时,同时按 Shift 和 Tab 将显示一个带有函数签名和简短描述的窗口。 按这些键两次将展开文档,按三下将在屏幕底部打开一个完整的窗口,其中包含相同的信息。
- 在单元格中,键入 ?func_name 并执行将打开一个窗口,其中包含函数签名和简短描述。
- 在单元格中,输入 ??func_name 并执行将打开一个窗口,其中包含函数签名、简短描述和源代码。
- 如果您使用的是 fastai 库,我们为您添加了一个 doc 函数:在单元格中执行 doc(func_name) 将打开一个窗口,其中包含该函数的签名、简短说明以及 GitHub 上源代码和完整文档的链接 库文档中的函数。
- 与文档无关,但仍然非常有用:如果遇到错误,要随时获取帮助,请在下一个单元格中键入 %debug 并执行以打开 Python 调试器,这将让您检查每个变量的内容。
结束侧边栏
在此过程中需要注意的一点是:正如我们在<>中讨论的那样,模型只能反映用于训练它们的数据。 世界上充满了有偏见的数据,这些数据最终反映在 Bing 图像搜索(我们用它来创建数据集)中。 例如,假设您有兴趣创建一个可以帮助用户确定他们是否拥有健康皮肤的应用程序,因此您根据“健康皮肤”的搜索结果训练了一个模型。 <> 向您显示您将获得的结果类型。
以此作为您的训练数据,您最终得到的不是一个健康的皮肤检测器,而是一个年轻的白人女性触摸她的面部检测器! 请务必仔细考虑您可能期望在应用程序中实际看到的数据类型,并仔细检查以确保所有这些类型都反映在模型的源数据中。 脚注:[感谢 Deb Raji,她提出了“健康皮肤”的例子。 请参阅她的论文“可操作的审计:调查商业人工智能产品公开命名有偏见的性能结果的影响”,了解有关模型偏见的更多精彩见解。]
现在我们已经下载了一些数据,我们需要将其组装成适合模型训练的格式。 在 fastai 中,这意味着创建一个名为 DataLoaders 的对象。
从数据到数据加载器
DataLoaders 是一个轻便类,它只存储您传递给它的任何 DataLoader 对象,并使它们可作为训练和有效的对象。 虽然它是一个非常简单的类,但它在 fastai 中非常重要:它为您的模型提供数据。 DataLoaders 中的关键功能仅由这四行代码提供(它还有一些其他次要功能,我们现在将跳过):
class DataLoaders(GetAttr):
def __init__(self, *loaders): self.loaders = loaders
def __getitem__(self, i): return self.loaders[i]
train,valid = add_props(lambda i,self: self[i])
行话:DataLoaders:一个 fastai 类,用于存储传递给它的多个 DataLoader 对象,通常是一个火车和一个有效的对象,尽管您可以拥有任意数量的对象。 前两个作为属性提供。
在本书的后面,您还将了解 Dataset 和 Datasets 类,它们具有相同的关系。
要将下载的数据转换为 DataLoaders 对象,我们需要告诉 fastai 至少四件事:
- 我们正在处理哪些类型的数据
- 如何获取项目列表
- 如何标记这些物品
- 如何创建验证集
到目前为止,我们已经看到了许多用于这些事物的特定组合的工厂方法,当您拥有恰好适合这些预定义方法的应用程序和数据结构时,这会很方便。 如果您不这样做,fastai 有一个非常灵活的系统,称为数据块 API。 使用此 API,您可以完全自定义 DataLoaders 创建的每个阶段。 以下是我们为刚刚下载的数据集创建 DataLoaders 所需的内容:
bears = DataBlock(
blocks=(ImageBlock, CategoryBlock),
get_items=get_image_files,
splitter=RandomSplitter(valid_pct=0.2, seed=42),
get_y=parent_label,
item_tfms=Resize(128))
让我们依次看看这些论点。 首先,我们提供一个元组,在其中指定自变量和因变量的类型:
blocks=(ImageBlock, CategoryBlock)
自变量是我们用来进行预测的变量,因变量是我们的目标。 在这种情况下,我们的自变量是图像,我们的因变量是每个图像的类别(熊的类型)。 我们将在本书的其余部分看到许多其他类型的块。
对于这个 DataLoader,我们的底层项目将是文件路径。 我们必须告诉 fastai 如何获取这些文件的列表。 get_image_files 函数采用一个路径,并返回该路径中所有图像的列表(默认情况下递归):
get_items=get_image_files
通常,您下载的数据集已经定义了验证集。 有时,这是通过将训练集和验证集的图像放入不同的文件夹中来完成的。 有时,它是通过提供一个 CSV 文件来完成的,其中列出了每个文件名及其应位于的数据集。可以通过多种方法来完成此操作,fastai 提供了一种非常通用的方法,允许您使用其预定义的方法之一 为此类,或者编写自己的类。 然而,在这种情况下,我们只想随机分割训练集和验证集。 但是,我们希望每次运行此笔记本时都有相同的训练/验证分割,因此我们修复了随机种子(计算机根本不知道如何创建随机数,而只是创建看起来随机的数字列表) ;如果您每次为该列表提供相同的起点(称为种子),那么您每次都会得到完全相同的列表):
splitter = RandomSplitter(valid_pct=0.2, seed=42)
自变量通常称为 x,因变量通常称为 y。 在这里,我们告诉 fastai 调用什么函数来在数据集中创建标签:
get_y=parent_label
Parent_label 是 fastai 提供的一个函数,它只是获取文件所在文件夹的名称。因为我们根据熊的类型将每个熊图像放入文件夹中,所以这将为我们提供所需的标签。
我们的图像大小各不相同,这对于深度学习来说是一个问题:我们不会一次向模型提供一张图像,而是提供多张图像(我们称之为小批量)。 要将它们分组到将通过我们的模型的大数组(通常称为张量)中,它们都需要具有相同的大小。 因此,我们需要添加一个变换,将这些图像的大小调整为相同的大小。 项目转换是在每个单独项目(无论是图像、类别等)上运行的代码片段。 fastai 包含许多预定义的变换; 我们在这里使用调整大小变换:
item_tfms=Resize(128)
该命令给了我们一个 DataBlock 对象。 这就像创建 DataLoaders 的模板。 我们仍然需要告诉 fastai 数据的实际来源——在本例中,是可以找到图像的路径:
dls = bears.dataloaders(path)
DataLoaders 包括验证和训练 DataLoaders。 DataLoader 是一个每次向 GPU 批量提供几个项目的类。 我们将在下一章中了解有关此类的更多信息。 当您循环访问 DataLoader 时,fastai 将一次为您提供 64 个(默认)项目,所有项目都堆叠到一个张量中。 我们可以通过调用 DataLoader 上的 show_batch 方法来查看其中的一些项目:
dls.valid.show_batch(max_n=4, nrows=1)
默认情况下,“调整大小”会使用完整宽度或高度裁剪图像,以适合所需尺寸的正方形。 这可能会导致丢失一些重要的细节。 或者,您可以要求 fastai 用零(黑色)填充图像,或挤压/拉伸它们:
bears = bears.new(item_tfms=Resize(128, ResizeMethod.Squish))
dls = bears.dataloaders(path)
dls.valid.show_batch(max_n=4, nrows=1)
bears = bears.new(item_tfms=Resize(128, ResizeMethod.Pad, pad_mode='zeros'))
dls = bears.dataloaders(path)
dls.valid.show_batch(max_n=4, nrows=1)
所有这些方法似乎都有点浪费或有问题。 如果我们挤压或拉伸图像,它们最终会变成不切实际的形状,从而导致模型了解到事物看起来与实际情况不同,我们预计这会导致准确性降低。 如果我们裁剪图像,那么我们就会删除一些允许我们执行识别的特征。 例如,如果我们试图识别狗或猫的品种,我们最终可能会剪掉区分相似品种所必需的身体或面部的关键部分。 如果我们填充图像,那么我们就会有大量的空白空间,这对我们的模型来说只是浪费计算,并导致我们实际使用的图像部分的有效分辨率较低。
相反,我们在实践中通常做的是随机选择图像的一部分,然后裁剪到该部分。 在每个时期(这是对数据集中所有图像的一次完整遍历),我们随机选择每个图像的不同部分。 这意味着我们的模型可以学习关注并识别图像中的不同特征。 它还反映了图像在现实世界中的工作方式:同一事物的不同照片可能以略有不同的方式进行构图。
事实上,完全未经训练的神经网络对图像的行为一无所知。 它甚至没有意识到,当一个物体旋转一度时,它仍然是同一物体的图片! 因此,实际上用图像示例来训练神经网络,其中对象的位置和大小略有不同,有助于它理解对象是什么以及如何在图像中表示对象的基本概念。
这是另一个示例,我们用 RandomResizedCrop 替换 Resize,这是提供我们刚才描述的行为的转换。 传入的最重要的参数是 min_scale,它决定每次至少选择多少图像:
bears = bears.new(item_tfms=RandomResizedCrop(128, min_scale=0.3))
dls = bears.dataloaders(path)
dls.train.show_batch(max_n=4, nrows=1, unique=True)
我们使用 unique=True 来使用此 RandomResizedCrop 变换的不同版本重复相同的图像。 这是一种更通用的技术(称为数据增强)的具体示例。
数据增强
数据增强是指创建输入数据的随机变化,使它们看起来不同,但实际上不会改变数据的含义。 常见的图像数据增强技术的例子有旋转、翻转、透视扭曲、亮度变化和对比度变化。 对于自然照片图像(例如我们在这里使用的图像),aug_transforms 函数提供了一组我们发现效果很好的标准增强功能。 因为我们的图像现在大小相同,所以我们可以使用 GPU 将这些增强应用到整批图像,这将节省大量时间。 为了告诉 fastai 我们想要在批量上使用这些变换,我们使用了batch_tfms 参数(请注意,在本例中我们没有使用 RandomResizedCrop,因此您可以更清楚地看到差异;我们还使用了双倍的增强量 出于同样的原因,与默认值相比):
bears = bears.new(item_tfms=Resize(128), batch_tfms=aug_transforms(mult=2))
dls = bears.dataloaders(path)
dls.train.show_batch(max_n=8, nrows=2, unique=True)
现在我们已经以适合模型训练的格式组装了数据,让我们实际使用它来训练图像分类器。
训练您的模型,并使用它来清理您的数据
是时候使用与 <> 中相同的代码行来训练我们的熊分类器了。
我们没有足够的数据来解决我们的问题(每种熊最多 150 张图片),因此为了训练我们的模型,我们将使用图像大小为 224 像素的 RandomResizedCrop,这是图像分类的相当标准 和默认的 aug_transforms:
bears = bears.new(
item_tfms=RandomResizedCrop(224, min_scale=0.5),
batch_tfms=aug_transforms())
dls = bears.dataloaders(path)
我们现在可以创建学习器并以通常的方式对其进行微调:
learn = vision_learner(dls, resnet18, metrics=error_rate)
learn.fine_tune(4)
现在让我们看看模型所犯的错误是否主要是认为灰熊是泰迪熊(这对安全不利!),或者认为灰熊是黑熊,或者其他什么。 为了形象化这一点,我们可以创建一个混淆矩阵:
interp = ClassificationInterpretation.from_learner(learn)
interp.plot_confusion_matrix()
这些行分别代表数据集中的所有黑熊、灰熊和泰迪熊。 这些列分别代表模型预测为黑色、灰熊和泰迪熊的图像。 因此,矩阵的对角线表示分类正确的图像,非对角线单元表示分类错误的图像。 这是 fastai 允许您查看模型结果的多种方式之一。 它(当然!)是使用验证集计算的。 通过颜色编码,目标是除了对角线之外的所有地方都有白色,我们需要深蓝色。 我们的熊分类器没有犯很多错误!
看看我们的错误究竟发生在哪里,看看它们是否是由于数据集问题(例如,根本不是熊的图像,或者标记不正确等),或者是模型问题(也许是),这很有帮助。 不处理在不寻常的光线下或从不同的角度等拍摄的图像)。 为此,我们可以根据图像的损失对图像进行排序。
如果模型不正确(特别是如果模型也对其错误答案有信心),或者模型是正确的,但对其正确答案没有信心,则损失的数字会更高。 在几章中,我们将深入学习如何在训练过程中计算和使用损失。 目前,plot_top_losses 向我们展示了数据集中损失最高的图像。 正如输出的标题所示,每个图像都标记有四个内容:预测、实际(目标标签)、损失和概率。 这里的概率是模型分配给其预测的置信度(从 0 到 1):
interp.plot_top_losses(5, nrows=1)
此输出显示损失最高的图像是被高置信度预测为“灰熊”的图像。 然而,它被标记为“黑色”(基于我们的 Bing 图像搜索)。 我们不是熊专家,但在我们看来,这个标签确实不正确! 我们或许应该将其标签更改为“grizzly”。
进行数据清理的直观方法是在训练模型之前进行。 但正如您在本例中所看到的,模型实际上可以帮助您更快、更轻松地发现数据问题。 所以,我们通常更喜欢先训练一个快速简单的模型,然后用它来帮助我们进行数据清理。
fastai 包含一个名为 ImageClassifierCleaner 的方便的数据清理 GUI,允许您选择类别、训练集与验证集并查看损失最高的图像(按顺序),以及允许选择图像进行删除或重新标记的菜单:
#hide_output
cleaner = ImageClassifierCleaner(learn)
cleaner
VBox(children=(Dropdown(options=(‘black’, ‘grizzly’, ‘teddy’), value=‘black’), Dropdown(options=(‘Train’, 'Val…
#hide
# for idx in cleaner.delete(): cleaner.fns[idx].unlink()
# for idx,cat in cleaner.change(): shutil.move(str(cleaner.fns[idx]), path/cat)
我们可以看到,在“黑熊”中,有一个图像包含两只熊:一只灰熊,一只黑色。 因此,我们应该在该图像下的菜单中选择<删除>。 ImageClassifierCleaner 实际上并不为您删除或更改标签;而是为您执行标签删除操作。 它只是返回要更改的项目的索引。 因此,例如,要删除(取消链接)所有选择删除的图像,我们将运行:
for idx in cleaner.delete(): cleaner.fns[idx].unlink()
要移动我们选择不同类别的图像,我们将运行:
for idx,cat in cleaner.change(): shutil.move(str(cleaner.fns[idx]), path/cat)
s:清理数据并为模型做好准备是数据科学家面临的两个最大挑战; 他们说这需要他们90%的时间。 fastai 库旨在提供使其尽可能简单的工具。
我们将在本书中看到更多模型驱动数据清理的示例。 一旦我们清理了数据,我们就可以重新训练我们的模型。 自己尝试一下,看看您的准确性是否有所提高!
注意:不需要大数据:使用这些步骤清理数据集后,我们通常会看到此任务的准确性为 100%。 当我们下载的图像比我们在这里使用的每类 150 张少得多时,我们甚至会看到这个结果。 正如您所看到的,人们普遍抱怨需要大量数据来进行深度学习,但这可能与事实相去甚远!
现在我们已经训练了我们的模型,让我们看看如何部署它以在实践中使用。
将您的模型转变为在线应用程序
我们现在将研究如何将该模型转变为可运行的在线应用程序。 我们只会创建一个基本的工作原型; 本书没有足够的篇幅来教您一般 Web 应用程序开发的所有细节。
使用模型进行推理
获得满意的模型后,您需要保存它,以便可以将其复制到将在生产中使用它的服务器。 请记住,模型由两部分组成:架构和训练参数。 保存模型的最简单方法是保存这两者,因为这样当您加载模型时,您可以确保拥有匹配的架构和参数。 要保存这两个部分,请使用导出方法。
此方法甚至保存了如何创建 DataLoader 的定义。 这很重要,因为否则您将不得不重新定义如何转换数据才能在生产中使用模型。 fastai 默认情况下会自动使用您的验证集 DataLoader 进行推理,因此不会应用您的数据增强,这通常是您想要的。
learn.export()
让我们使用 fastai 添加到 Python Path 类的 ls 方法来检查该文件是否存在:
path = Path()
path.ls(file_exts='.pkl')
无论您将应用程序部署到何处,都将需要此文件。 现在,让我们尝试在笔记本中创建一个简单的应用程序。
当我们使用模型来获得预测而不是训练时,我们将其称为推理。 为了从导出的文件创建我们的推理学习器,我们使用 load_learner (在这种情况下,这并不是真正必要的,因为我们的笔记本中已经有一个正在工作的学习器;我们只是在这里这样做,以便您可以看到整个过程 端到端):
learn_inf = load_learner(path/'export.pkl')
当我们进行推理时,我们通常一次只获得一张图像的预测。 为此,请传递文件名进行预测:
learn_inf.predict('images/grizzly.jpg')
(‘grizzly’, tensor(1), tensor([9.0767e-06, 9.9999e-01, 1.5748e-07]))
这返回了三件事:与您最初提供的格式相同的预测类别(在本例中是一个字符串)、预测类别的索引以及每个类别的概率。 最后两个基于 DataLoaders 词汇中的类别顺序; 即所有可能类别的存储列表。 在推理时,您可以访问 DataLoaders 作为 Learner 的属性:
learn_inf.dls.vocab
(#3) [‘black’,‘grizzly’,‘teddy’]
我们可以在这里看到,如果我们使用预测返回的整数对词汇进行索引,那么我们将按照预期返回“grizzly”。 另请注意,如果我们对概率列表进行索引,我们会发现这是一只灰熊的概率接近 1.00。
我们知道如何根据保存的模型进行预测,因此我们拥有开始构建应用程序所需的一切。 我们可以直接在 Jupyter Notebook 中完成。
从模型创建笔记本应用程序
要在应用程序中使用我们的模型,我们可以简单地将预测方法视为常规函数。 因此,可以使用应用程序开发人员可用的无数框架和技术中的任何一种来从模型创建应用程序。
然而,大多数数据科学家并不熟悉 Web 应用程序开发的世界。 因此,让我们尝试使用您现在所做的事情,知道:事实证明,我们可以只使用 Jupyter 笔记本来创建一个完整的工作 Web 应用程序! 为了实现这一目标,我们需要做两件事:
- IPython widgets (ipywidgets)
- Voilà
IPython widgets 是 GUI 组件,它将 Web 浏览器中的 JavaScript 和 Python 功能结合在一起,可以在 Jupyter Notebook 中创建和使用。 例如,我们在本章前面看到的图像清理器完全是用 IPython 小部件编写的。 但是,我们不希望要求应用程序的用户自己运行 Jupyter。
这就是 Voilà 存在的原因。 它是一个系统,用于使由 IPython widgets 组成的应用程序可供最终用户使用,而他们根本不需要使用 Jupyter。 Voilà 正在利用这样一个事实:笔记本已经是一种 Web 应用程序,只是一个相当复杂的应用程序,依赖于另一个 Web 应用程序:Jupyter 本身。 从本质上讲,它可以帮助我们自动将已经隐式制作的复杂 Web 应用程序(笔记本)转换为更简单、更易于部署的 Web 应用程序,其功能类似于普通的 Web 应用程序,而不是笔记本。
但我们仍然有在笔记本上开发的优势,因此使用 ipywidgets,我们可以逐步构建我们的 GUI。 我们将使用这种方法创建一个简单的图像分类器。 首先,我们需要一个文件上传小部件:
#hide_output
btn_upload = widgets.FileUpload()
btn_upload
FileUpload(value={}, description=‘Upload’)
现在我们可以抓取图像:
#hide
# 对于这本书,我们实际上无法点击上传按钮,所以我们伪造它
btn_upload = SimpleNamespace(data = ['images/grizzly.jpg'])
img = PILImage.create(btn_upload.data[-1])
我们可以使用输出小部件来显示它:
#hide_output
out_pl = widgets.Output()
out_pl.clear_output()
with out_pl: display(img.to_thumb(128,128))
out_pl
然后我们就可以得到我们的预测:
pred,pred_idx,probs = learn_inf.predict(img)
并使用标签来显示它们:
#hide_output
out_pl = widgets.Output()
out_pl.clear_output()
with out_pl: display(img.to_thumb(128,128))
out_pl
然后我们就可以得到我们的预测:
pred,pred_idx,probs = learn_inf.predict(img)
并使用标签来显示它们:
#hide_output
lbl_pred = widgets.Label()
lbl_pred.value = f'Prediction: {pred}; Probability: {probs[pred_idx]:.04f}'
lbl_pred
Label(value=‘Prediction: grizzly; Probability: 1.0000’)
我们需要一个按钮来进行分类。 它看起来和上传按钮一模一样:
#hide_output
btn_run = widgets.Button(description='Classify')
btn_run
Button(description=‘Classify’, style=ButtonStyle())
我们还需要一个点击事件处理程序; 也就是说,按下时将调用的函数。 我们可以复制上面的代码行:
def on_click_classify(change):
img = PILImage.create(btn_upload.data[-1])
out_pl.clear_output()
with out_pl: display(img.to_thumb(128,128))
pred,pred_idx,probs = learn_inf.predict(img)
lbl_pred.value = f'Prediction: {pred}; Probability: {probs[pred_idx]:.04f}'
btn_run.on_click(on_click_classify)
您现在可以通过按下按钮来测试该按钮,您应该会看到图像和预测自动更新!
现在我们可以将它们全部放在一个垂直框 (VBox) 中来完成我们的 GUI:
#hide
#将 btn_upload 放回到下一个单元格的小部件中
btn_upload = widgets.FileUpload()
#hide_output
VBox([widgets.Label('Select your bear!'),
btn_upload, btn_run, out_pl, lbl_pred])
VBox(children=(Label(value=‘Select your bear!’), FileUpload(value={}, description=‘Upload’), Button(descriptio…
我们已经编写了应用程序所需的所有代码。 下一步是将其转换为我们可以部署的东西。
将您的笔记本变成真正的应用程序
#hide
# !pip install voila
# !jupyter serverextension enable --sys-prefix voila
现在我们已经在 Jupyter Notebook 中完成了所有工作,我们可以创建我们的应用程序了。 为此,请启动一个新笔记本,然后仅向其中添加创建和显示所需小部件所需的代码,以及要显示的任何文本的 Markdown。 查看本书存储库中的 bear_classifier 笔记本,了解我们创建的简单笔记本应用程序。
接下来,如果您还没有安装 Voilà,请将这些行复制到笔记本单元中并执行它:
!pip install voila
!jupyter serverextension enable --sys-prefix voila
以 ! 开头的单元格 不包含 Python 代码,而是包含传递到 shell(bash、Windows PowerShell 等)的代码。 如果您习惯使用命令行(我们将在本书后面详细讨论),您当然可以直接在终端中直接键入这两行(不带 ! 前缀)。 在本例中,第一行安装瞧库和应用程序,第二行将其连接到现有的 Jupyter 笔记本。
Voilà 运行 Jupyter 笔记本就像您现在使用的 Jupyter 笔记本服务器一样,但它还做了一些非常重要的事情:它删除了所有单元格输入,并且仅显示输出(包括 ipywidgets)以及您的 Markdown 单元格。 那么剩下的就是 Web 应用程序了! 要将您的笔记本作为 Voilà Web 应用程序查看,请将浏览器 URL 中的“notebooks”一词替换为:“voila/render”。 您将看到与笔记本相同的内容,但没有任何代码单元格。
当然,您不需要使用 Voilà 或 ipywidgets。 您的模型只是一个可以调用的函数 (pred,pred_idx,probs = learn.predict(img)),因此您可以将其与托管在任何平台上的任何框架一起使用。 您可以采用在 ipywidgets 中原型化的东西,然后将其转换为常规 Web 应用程序。 我们在书中向您展示这种方法是因为我们认为对于数据科学家和其他非 Web 开发专家的人来说,这是从他们的模型创建应用程序的好方法。
我们已经有了我们的应用程序,现在让我们部署它!
部署您的应用程序
正如您现在所知,您需要 GPU 来训练几乎任何有用的深度学习模型。 那么,您是否需要 GPU 才能在生产中使用该模型? 不! 您几乎肯定不需要 GPU 来为生产中的模型提供服务。 造成这种情况的原因有以下几个:
- 正如我们所见,GPU 仅在并行执行大量相同工作时才有用。 如果您正在进行(比如说)图像分类,那么您通常一次只会对一个用户的图像进行分类,并且通常没有足够的工作可以在单个图像中完成,以保持 GPU 忙碌足够长的时间。 非常高效。 因此,CPU 通常会更具成本效益。
- 另一种方法是等待一些用户提交他们的图像,然后将它们批量处理并在 GPU 上一次性处理它们。 但随后您要求用户等待,而不是立即获得答案! 您需要一个大容量的网站才能使其可行。 如果您确实需要此功能,可以使用 Microsoft 的 ONNX Runtime 或 AWS Sagemaker 等工具
- 处理 GPU 推理的复杂性非常高。 特别是,GPU 的内存需要仔细的手动管理,并且您需要一个仔细的排队系统来确保一次只处理一批。
- CPU 的市场竞争比 GPU 服务器激烈得多,因此 CPU 服务器有更便宜的选择。
由于 GPU 服务的复杂性,许多系统如雨后春笋般涌现,试图将其自动化。 然而,管理和运行这些系统也很复杂,通常需要将模型编译成专门针对该系统的不同形式。 通常最好避免处理这种复杂性,直到/除非您的应用程序足够流行,以至于这样做对您来说具有明确的财务意义。
至少对于您的应用程序的初始原型,以及您想要展示的任何业余爱好项目,您都可以轻松免费托管它们。 执行此操作的最佳地点和最佳方法会随着时间的推移而变化,因此请查看本书的网站以获取最新的建议。 当我们在 2020 年初写这本书时,最简单(而且免费!)的方法是使用 Binder。 要在 Binder 上发布您的 Web 应用程序,请执行以下步骤:
- 将您的笔记本添加到 GitHub 存储库。
- 将该存储库的 URL 粘贴到 Binder 的 URL 中,如 <> 所示。
- 更改文件下拉列表以选择 URL。
- 在“要打开的 URL”字段中,输入 /voila/render/name.ipynb (将 name 替换为笔记本的名称)。
- 单击右下角的点击板按钮复制 URL 并将其粘贴到安全的地方。
- 单击启动。
第一次执行此操作时,Binder 将花费大约 5 分钟来构建您的网站。 在幕后,它会寻找一个可以运行您的应用程序的虚拟机、分配存储空间、收集 Jupyter、您的笔记本所需的文件以及将您的笔记本呈现为 Web 应用程序。
最后,一旦应用程序开始运行,它会将您的浏览器导航到新的网络应用程序。 您可以共享您复制的 URL,以允许其他人也访问您的应用程序。
对于部署网络应用程序的其他(免费和付费)选项,请务必查看本书的网站。
您可能很想将应用程序部署到移动设备或边缘设备(例如 Raspberry Pi)上。 有很多库和框架允许您将模型直接集成到移动应用程序中。 然而,这些方法往往需要大量额外的步骤和样板,并且并不总是支持模型可能使用的所有 PyTorch 和 fastai 层。 此外,您所做的工作将取决于您要部署的移动设备类型 - 您可能需要执行一些工作以在 iOS 设备上运行,在较新的 Android 设备上运行不同的工作,在较旧的 Android 设备上运行不同的工作, 相反,我们建议您尽可能将模型本身部署到服务器,并让您的移动或边缘应用程序作为 Web 服务连接到它。
这种方法有很多优点。 初始安装更容易,因为您只需部署一个小型 GUI 应用程序,该应用程序连接到服务器来完成所有繁重的工作。 也许更重要的是,该核心逻辑的升级可以在您的服务器上进行,而不需要分发给所有用户。 您的服务器将比大多数边缘设备拥有更多的内存和处理能力,并且如果您的模型要求更高,则扩展这些资源要容易得多。 服务器上的硬件也将更加标准,并且更容易受到 fastai 和 PyTorch 的支持,因此您不必将模型编译为不同的形式。
当然,也有缺点。 您的应用程序需要网络连接,并且每次调用模型时都会有一些延迟。 (无论如何,神经网络模型都需要一段时间才能运行,因此这种额外的网络延迟在实践中可能不会对您的用户产生太大影响。事实上,由于您可以在服务器上使用更好的硬件,因此总体延迟甚至可能是 少于在本地运行的情况!)此外,如果您的应用程序使用敏感数据,那么您的用户可能会担心将该数据发送到远程服务器的方法,因此有时隐私考虑意味着您需要在远程服务器上运行模型 边缘设备(可以通过拥有本地服务器(例如在公司的防火墙内)来避免这种情况)。 管理复杂性和扩展服务器也会产生额外的开销,而如果您的模型在边缘设备上运行,那么每个用户都会带来自己的计算资源,这会导致随着用户数量的增加而更轻松地进行扩展(也称为水平扩展) 。
A:我有机会在工作中近距离观察移动机器学习领域正在发生的变化。 我们提供一款依赖于计算机视觉的 iPhone 应用程序,多年来我们在云端运行我们自己的计算机视觉模型。 这是当时唯一的方法,因为这些模型需要大量的内存和计算资源,并且需要几分钟来处理输入。 这种方法不仅需要构建模型(有趣!),还需要构建基础设施,以确保一定数量的“计算工作机器”绝对始终运行(可怕),如果流量增加,更多机器会自动上线,并且稳定 存储大量输入和输出,iOS 应用程序可以知道并告诉用户他们的工作进展如何,等等。如今,Apple 提供了用于转换模型以在设备上高效运行的 API,并且大多数 iOS 设备都有专用的 ML 硬件,所以这就是策略 我们用于新型号。 这仍然不容易,但就我们而言,为了更快的用户体验并减少对服务器的担心,这是值得的。 实际上,什么对您有效取决于您想要创建的用户体验以及您个人认为容易做到的事情。 如果您真的知道如何运行服务器,那就去做吧。 如果您真的知道如何构建本机移动应用程序,那就去做吧。 上山的路有很多条。
总的来说,我们建议尽可能使用简单的基于 CPU 的服务器方法,只要您能摆脱它。 如果您足够幸运,拥有一个非常成功的应用程序,那么您将能够证明当时对更复杂的部署方法的投资是合理的。
恭喜您,您已经成功构建并部署了深度学习模型! 现在是暂停一下并思考可能出现问题的好时机。
如何避免灾难
在实践中,深度学习模型只是一个更大系统的一部分。 正如我们在本章开头所讨论的,数据产品需要考虑整个端到端流程,从概念到生产中的使用。 在本书中,我们不能指望涵盖管理已部署数据产品的所有复杂性,例如管理模型的多个版本、A/B 测试、金丝雀、刷新数据(我们是否应该一直不断地增长我们的数据集) ,或者我们应该定期删除一些旧数据?),处理数据标签,监控所有这些,检测模型腐烂,等等。 在本节中,我们将概述一些需要考虑的最重要问题; 有关部署问题的更详细讨论,请参阅 Emmanuel Ameisen (O’Reilly) 撰写的优秀的《构建机器学习驱动的应用程序》
需要考虑的最大问题之一是理解和测试深度学习模型的行为比您编写的大多数其他代码要困难得多。 通过正常的软件开发,您可以分析软件正在执行的确切步骤,并仔细研究哪些步骤与您尝试创建的所需行为相匹配。 但对于神经网络来说,行为是由模型尝试匹配训练数据而产生的,而不是被精确定义的。
这可能会导致灾难! 例如,假设我们确实正在推出一个熊检测系统,该系统将连接到国家公园露营地周围的摄像机上,并向露营者发出熊即将到来的警告。 如果我们使用用我们下载的数据集训练的模型,那么在实践中就会出现各种各样的问题,例如:
- 使用视频数据而不是图像
- 处理夜间图像,这些图像可能不会出现在该数据集中
- 处理低分辨率相机图像
- 确保结果返回得足够快,以便在实践中有用
- 识别人们在网上发布的照片中很少看到的位置的熊(例如从后面、部分被灌木丛覆盖或距离相机很远)
问题的一个重要部分是,人们最有可能上传到互联网上的照片类型是能够很好地清晰且艺术地展示其主题的照片,而这不是这种类型的输入 系统将会得到。 因此,我们可能需要自己进行大量数据收集和标记来创建一个有用的系统。
这只是域外数据这一更普遍问题的一个例子。 也就是说,我们的模型在生产中看到的数据可能与训练期间看到的数据非常不同。 这个问题实际上并没有完整的技术解决方案; 相反,我们必须谨慎对待推出该技术的方法。
还有其他原因我们也需要小心。 一个非常常见的问题是域转移,即我们的模型看到的数据类型随着时间的推移而变化。 例如,保险公司可能会使用深度学习模型作为其定价和风险算法的一部分,但随着时间的推移,公司吸引的客户类型及其代表的风险类型可能会发生很大变化,以至于原始训练数据 不再相关。
域外数据和域转移是一个更大问题的例子:你永远无法完全理解神经网络的整个行为。 它们有太多的参数,无法分析地理解它们所有可能的行为。 这是它们最好的特性的自然缺点——灵活性,这使它们能够解决复杂的问题,而我们甚至可能无法完全指定我们首选的解决方法。 然而,好消息是,有一些方法可以通过仔细考虑的流程来减轻这些风险。 其细节将根据您要解决的问题的细节而有所不同,但我们将尝试在此提出一种高级方法,在 <> 中进行了总结,我们希望它将提供有用的指导。
在可能的情况下,第一步是使用完全手动的过程,深度学习模型方法并行运行,但不直接用于驱动任何操作。 参与手动过程的人员应该查看深度学习的输出并检查它们是否有意义。 例如,使用我们的熊分类器,公园管理员可以在屏幕上显示所有摄像机的视频源,任何可能的熊目击事件都会简单地以红色突出显示。 公园管理员仍然需要像部署模型之前一样保持警惕; 该模型此时只是帮助检查问题。
第二步是尝试限制模型的范围,并由人们仔细监督。 例如,对模型驱动方法进行一次小型的地理和时间限制试验。 我们不必在全国每个国家公园都推出我们的熊分类器,而是可以选择一个观察站,为期一周,并让公园管理员在每个警报发出之前检查它。
然后,逐渐扩大部署范围。 当您这样做时,请确保您拥有真正良好的报告系统,以确保您了解与手动流程相比所采取的操作的任何重大变化。 例如,如果在某些地方推出新系统后,熊警报的数量增加了一倍或一半,我们应该非常担心。 尝试考虑系统可能出错的所有方式,然后考虑哪些措施、报告或图片可以反映该问题,并确保您的定期报告包含该信息。
J:20 年前,我创办了一家名为 Optimal Decisions 的公司,该公司利用机器学习和优化来帮助大型保险公司制定定价,影响数百亿美元的风险。 我们使用此处描述的方法来管理出现问题的潜在负面影响。 此外,在与客户合作将任何内容投入生产之前,我们尝试通过根据前一年的数据测试端到端系统来模拟影响。 将这些新算法投入生产总是一个相当令人伤脑筋的过程,但每次推出都是成功的。
不可预见的后果和反馈循环
推出模型的最大挑战之一是您的模型可能会改变它所属系统的行为。 例如,考虑一种“预测警务”算法,该算法预测某些社区会发生更多犯罪,导致更多警察被派往这些社区,这可能会导致这些社区记录更多犯罪,等等。 在皇家统计学会论文《预测和服务?》中,克里斯蒂安·卢姆和威廉·艾萨克指出:“预测性警务的名称恰如其分:它预测未来的警务工作,而不是未来的犯罪。”
这种情况下的部分问题是,在存在偏见的情况下(我们将在下一章深入讨论),反馈循环可能会导致偏见的负面影响变得越来越严重。 例如,有人担心这种情况已经在美国发生,美国的逮捕率因种族原因存在显着偏见。 据美国公民自由联盟称,“尽管吸食率大致相同,黑人因大麻被捕的可能性是白人的 3.73 倍。” 这种偏见的影响,以及预测警务算法在美国许多地区的推出,导致巴里·威廉姆斯 (Bärí Williams) 在《纽约时报》上写道: 执法方式可能意味着,在未来几年里,我现在 7 岁的儿子更有可能因为他的种族和我们居住的地方而被定性或被捕,甚至更糟。”
在推出重要的机器学习系统之前,一个有用的练习是考虑这个问题:“如果它真的非常顺利,会发生什么?” 换句话说,如果预测能力非常高,并且其影响行为的能力非常显着怎么办? 那么,谁受影响最大呢? 最极端的结果可能是什么样子? 你怎么知道到底发生了什么?
这样的思考练习可能会帮助您制定更仔细的推出计划,并进行持续的监控系统和人工监督。 当然,如果不听取人的意见,人类监督就毫无用处,因此请确保有可靠且有弹性的沟通渠道,以便合适的人能够意识到问题,并有能力解决问题。
开始写作吧!
我们的学生发现,对于巩固他们对这些材料的理解最有帮助的事情之一就是把它写下来。 没有什么比尝试将某个主题教授给其他人更好地测试您对某个主题的理解了。 即使您从不向任何人展示您的作品,这也会很有帮助,但如果您分享它就更好了! 因此,如果您还没有创建博客,我们建议您创建一个博客。 现在您已经完成了第 2 章并学习了如何训练和部署模型,您已经准备好撰写有关深度学习之旅的第一篇博客文章了。 什么让你感到惊讶? 您认为深度学习在您的领域有哪些机会? 您看到什么障碍?
fast.ai 联合创始人 Rachel Thomas 在《Why You (Yes, You) Should Blog》一文中写道:
我给年轻时的自己最重要的建议是早点开始写博客。 以下是写博客的一些原因:
- 这就像一份简历,而且更好。 我认识一些人,他们的博客文章带来了工作机会!
- 帮助你学习。 整理知识总能帮助我综合自己的想法。 检验你是否理解某件事的标准之一就是你能否向别人解释它。 博客文章是做到这一点的好方法。
- 我从我的博客文章中收到了会议邀请和演讲邀请。 我受邀参加 TensorFlow 开发峰会(太棒了!),因为我写了一篇关于我如何不喜欢 TensorFlow 的博客文章。
- 认识新的人。 我遇到了几个回复我写的博客文章的人。
- 节省时间。 每当您通过电子邮件多次回答某个问题时,都应该将其变成博客文章,这样下次有人问时您就可以更轻松地分享。
也许她最重要的建议是:
:您最适合帮助落后您一步的人。 这些材料对你来说仍然记忆犹新。 许多专家已经忘记了作为初学者(或中级)的感觉,并且忘记了为什么当您第一次听到这个主题时很难理解它。 你的特定背景、你的特定风格和你的知识水平会给你所写的内容带来不同的变化。
我们已在 <> 中提供了有关如何设置博客的完整详细信息。 如果您还没有博客,请立即查看,因为我们为您设置了一个非常好的方法,让您可以免费开始写博客,没有广告,您甚至可以使用 Jupyter Notebook!