FastAI-深度学习秘籍-全-

FastAI 深度学习秘籍(全)

原文:annas-archive.org/md5/238a690f0344b1fc28d0f515ee7ae7ac

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

fastai 是一个易于使用的深度学习框架,建立在 PyTorch 之上,可以让你通过少量的代码快速创建完整的深度学习解决方案。如今,主流的低级深度学习框架(TensorFlow 和 PyTorch)即使是处理简单应用,也需要编写大量代码。相反,fastai 为你处理了繁琐的细节,让你能够专注于将深度学习应用于实际问题的解决。

我们将首先总结 fastai 的价值,并展示一个简单的“hello world”深度学习应用。然后,我们将描述如何使用 fastai 处理框架明确支持的四个应用领域:表格数据、文本数据(NLP)、推荐系统和视觉数据。你将通过一系列实际示例来学习如何创建每种类型的真实应用。之后,你将学习如何部署 fastai 模型。例如,你将学会如何创建一个简单的 Web 应用,预测图像中展示的物体。最后,我们将总结 fastai 的高级功能概述。

在本书结束时,你将能够使用 fastai 创建自己的深度学习应用。你将了解如何使用 fastai 准备原始数据集、探索数据集、训练深度学习模型并部署训练好的模型。

适合读者

本书适合数据科学家、机器学习开发人员和深度学习爱好者,旨在通过基于实例的方法学习和探索 fastai 框架。强烈推荐具备 Python 编程语言的基本知识和机器学习基础,以便充分利用本书内容。

本书提供了如何使用 fastai 解决多种深度学习应用领域的实际示例,但并不是该平台的详尽参考。要获取关于 fastai 的全面细节,请参阅第八章**,扩展的 fastai 和部署功能部分。此部分指向其他 fastai 内容,包括 Jeremy Howard 及其团队基于 fastai 创建的优秀深度学习课程。

本书涵盖的内容

第一章快速入门 fastai,教你如何为 fastai 设置环境,带你训练一个初始的hello world fastai 模型,解释 fastai 的四个关键应用领域(表格数据、文本数据、推荐系统和图像数据),并将 fastai 与另一个重要的高级深度学习框架 Keras 进行对比。

第二章使用 fastai 探索和清理数据,描述了 fastai 开箱即用的各类数据集(整理数据集);描述了如何检查表格、文本和图像数据集;并展示了如何使用 fastai 的工具清理数据集,例如,通过处理缺失值来清理数据。

第三章使用表格数据训练模型,解释了如何创建基于表格数据集(即按行和列排列的数据集)训练的 fastai 深度学习模型。本章中的示例展示了如何在整理数据集和独立数据集上训练 fastai 模型。

第四章使用文本数据训练模型,解释了如何创建基于文本数据集训练的 fastai 深度学习模型。本章中的示例展示了如何训练语言模型(即,给定一系列单词,预测下一个单词的模型),以及如何训练文本分类模型(即,预测例如某个评论是负面还是正面的模型)。本章涵盖了使用整理数据集和独立数据集训练的模型。

第五章训练推荐系统,解释了如何使用 fastai 创建推荐系统,即例如预测某个读者是否会喜欢某本书,基于其他读者为该书提供的评分。本章涵盖了使用整理数据集和独立数据集训练的推荐系统。

第六章使用视觉数据训练模型,解释了如何使用 fastai 创建基于图像数据集训练的深度学习模型。本章中的示例展示了如何为描述一个或多个对象的图像创建图像分类系统,训练数据包括经过整理的数据集和独立数据集。

第七章部署与模型维护,解释了如何将训练好的 fastai 模型部署到一个简单的 Web 应用程序中。本章中的示例展示了如何部署基于表格和图像数据集训练的 fastai 深度学习模型。本章还介绍了如何在模型部署后进行维护。

第八章扩展 fastai 和部署特性,解释了 fastai 的额外功能,包括对从第三章 使用表格数据训练模型第六章 使用视觉数据训练模型中引入的模型进行的增强,以及对第七章 部署与模型维护中介绍的部署技术的变体。

为了充分利用本书

为了充分利用本书,你应该对 Python 编程(在 Jupyter 笔记本和独立 Python 模块中)和机器学习的核心概念感到熟悉。本书解释了多种深度学习应用,但没有深入探讨深度学习的内部原理。如果你对深度学习的基本工作原理有所了解,你会发现书中的高级示例更容易理解。

本书中的大多数代码示例设计用于在支持 GPU 的云深度学习 Jupyter 笔记本环境中运行。你可以选择使用Paperspace GradientGoogle Colab来运行这些示例,推荐使用 Gradient 环境。第7 章《部署与模型维护》和第8 章《扩展 fastai 与部署功能》中的模型部署示例,设计用于在本地系统上运行,需要在本地安装 fastai 和 PyTorch。

如果你使用的是本书的数字版,建议你自己输入代码,或者通过 GitHub 仓库访问代码(链接将在下一节提供)。这样做可以帮助你避免与复制和粘贴代码相关的潜在错误。

下载示例代码文件

你可以从 GitHub 下载本书的示例代码文件,网址为github.com/PacktPublishing/Deep-Learning-with-fastai-Cookbook。如果代码有更新,它将在现有的 GitHub 仓库中进行更新。

我们还提供了其他的代码包,来自我们丰富的书籍和视频目录,网址:github.com/PacktPublishing/。快去看看吧!

下载彩色图像

我们还提供了一个 PDF 文件,里面包含了本书中使用的截图/图表的彩色图像。你可以在这里下载:https://static.packt-cdn.com/downloads/9781800208100_ColorImages.pdf。

使用的约定

本书中使用了一些文本约定。

文本中的代码:表示文本中的代码词汇、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名。例如:“在浏览器中访问localhost:5000以显示home.html。”

代码块设置如下:

for(var i = 0; i < relationship_list.length; i++) {
            var opt = relationship_list[i];
            select_relationship.innerHTML += "<option value=\"" + opt + "\">" + opt + "</option>";

任何命令行输入或输出均如下所示:

cp -r deploy_image deploy_image_test

粗体:表示新术语、重要词汇或你在屏幕上看到的词汇。例如,菜单或对话框中的词汇在文本中会这样显示。以下是一个示例:“选择选择文件按钮以打开文件选择对话框。”

提示或重要说明

以这种方式呈现。

章节

本书中会有一些经常出现的标题(准备工作、如何做…、它是如何工作的…、还有更多…和另见)。

为了给出清晰的完成食谱的指导,请按如下方式使用这些部分:

准备工作

本节告诉你在食谱中可以期待什么,并描述了如何设置任何软件或进行任何准备工作。

如何做…

本节包含执行食谱所需的步骤。

它是如何工作的…

本节通常是对前一节发生的事情的详细解释。

还有更多…

本节包含有关食谱的附加信息,以帮助你更好地理解食谱。

另见

本节提供了与食谱相关的其他有用信息的链接。

联系我们

我们始终欢迎读者的反馈。

一般反馈:如果你对本书的任何方面有疑问,请在邮件的主题中提到书名,并通过customercare@packtpub.com与我们联系。

勘误:尽管我们已尽一切努力确保内容的准确性,但错误还是难以避免。如果你在本书中发现了错误,我们将不胜感激你能向我们报告。请访问www.packtpub.com/support/errata,选择你的书籍,点击“勘误提交表格”链接并填写相关信息。

盗版:如果你在互联网上发现我们作品的任何非法副本,我们将不胜感激你能提供该副本的地址或网站名称。请通过版权@packt.com联系我们,并提供材料的链接。

如果你有兴趣成为作者:如果你在某个领域具有专业知识,并且有兴趣撰写或为一本书做贡献,请访问authors.packtpub.com

分享你的想法

阅读完Deep Learning with fastai Cookbook后,我们很想听听你的想法!请点击这里直接进入亚马逊评论页面,并分享你的反馈。

你的评论对我们以及技术社区至关重要,它将帮助我们确保提供优质的内容。

第一章:第一章: 快速入门 fastai

在过去的十年里,深度学习彻底改变了众多技术领域,从图像识别到机器翻译。直到最近,只有那些经过广泛训练并拥有专门硬件的人才能解锁深度学习的好处。fastai 框架旨在通过使深度学习对非专业人士更易获取,来实现深度学习的普及化。fastai 使得深度学习能够面向大众的关键方式之一就是让入门变得简单。

在本章中,我们将向你展示开始使用 fastai 所需的内容,从如何设置 fastai 的环境开始。到本章结束时,你将能够做到以下几点:设置一个用于运行fastai示例的云环境;练习一个基础的 fastai 示例;解释 fastai 与 PyTorch(fastai 的底层深度学习库)之间的关系;并对比 fastai 与 Keras,这个另一个高层次的深度学习库。

本章将涵盖以下内容:

  • 在 Paperspace Gradient 中设置 fastai 环境

  • 在 Google Colaboratory(Google Colab)中设置 fastai 环境

  • 在 Paperspace Gradient 中设置 JupyterLab 环境

  • fastai 的"Hello world"——为修改版国家科学技术研究所MNIST)数据集创建一个模型

  • 从四个应用领域了解世界:表格、文本、推荐系统和图像

  • 使用 PyTorch 张量

  • 对比 fastai 与 Keras

  • 测试你的知识

技术要求

本章中,你将使用以下技术:

你可以在以下链接找到本章提到的代码:

github.com/PacktPublishing/Deep-Learning-with-fastai-Cookbook/tree/main/ch1

在 Paperspace Gradient 中设置 fastai 环境

你可以使用两个免费的云环境来探索 fastai:Paperspace GradientGoogle Colab。在本节中,我们将介绍如何设置 Paperspace Gradient 与 fastai 笔记本环境,并在下一节中介绍 Colab 的设置步骤。这是你的选择,选择最适合你的环境。

Gradient 使用起来更简单,因为你可以访问一个标准的文件系统来进行存储。而在 Colab 中,你需要使用 Google Drive 来进行存储,并且与 Gradient 不同,你无法方便地访问终端来进行命令行交互。

另一方面,Colab 提供了比 fastai 更多的库,例如,你可以在 Colab 中运行 Keras MNIST 示例,但它无法直接在 Gradient fastai 实例中运行。为了充分利用本书中的示例,最好设置这两种环境,这样你可以根据需要选择最适合你的环境。我们将从 Gradient 开始,因为它是最容易入手的。

准备工作

在为 fastai 设置 Gradient 之前,你需要创建一个 Paperspace 账户。你可以通过访问 console.paperspace.com/signup?gradient=true 来完成注册。

如何操作…

一旦你有了 Paperspace 账户,就可以按照以下步骤在 Gradient 中创建一个免费的 fastai 笔记本。创建后,这将是一个完整的 Jupyter Notebook 环境,包含你需要的所有库(包括 fastai、PyTorch 及相关库)。

  1. 访问 Paperspace 网站并使用在 准备工作 部分创建的账户登录。

  2. 在页面顶部的下拉菜单中,选择 Gradient图 1.1 – 从下拉菜单中选择 Gradient

    图 1.1 – 从下拉菜单中选择 Gradient

  3. 选择 笔记本 标签:图 1.2 – 选择笔记本标签

    图 1.2 – 选择笔记本标签

  4. 选择 创建 按钮。图 1.3 – 创建按钮

    图 1.3 – 创建按钮

  5. 名称 字段中输入你的笔记本名称。

  6. 选择运行时 部分,选择 fastai

  7. 选择机器 部分,选择 Free-GPUFree-P5000。请注意,你可能会收到一条提示消息,说明你选择的机器类型超出了容量。如果发生这种情况,你可以选择另一个支持 GPU 的机器类型,或者稍等几分钟再试。如果你的笔记本已经创建完成,你还可以更改机器类型。例如,如果你发现免费的实例无法满足需求,可以将笔记本切换到付费机器。你还可以为不同的应用程序定义多个笔记本,并配置自动关机(即实例运行多少小时后自动关闭),如果你选择付费订阅。详细信息请参见 console.paperspace.com/teim6pi2i/upgrade

  8. 选择 启动笔记本 按钮,以在 Gradient 中为你创建一个新的 fastai 实例。图 1.4 – 启动笔记本按钮

    图 1.4 – 启动笔记本按钮

  9. 你的笔记本需要大约一分钟的时间来创建。当它准备好时,你会在屏幕底部看到 正在运行 消息:图 1.5 – 正在运行的消息

    图 1.5 – 正在运行的消息

  10. 接下来,你应该会在左侧的导航面板中看到一个 Jupyter 按钮,如 图 1.6 中所示:图 1.6 – 导航面板中的 Jupyter 图标

    图 1.6 – 导航面板中的 Jupyter 图标

  11. 选择 Jupyter 按钮以启动新的笔记本环境。你现在应该会看到 Jupyter 文件视图,如 图 1.7 所示:

图 1.7 – Gradient 中的 Jupyter 文件视图

图 1.7 – Gradient 中的 Jupyter 文件视图

现在你的笔记本已经启动,你需要通过运行一个简短的笔记本来验证它是否正确设置。此笔记本将检查你的笔记本上可用的 fastai 版本,并确认笔记本是否能够访问 图形处理单元GPU),这是后续示例高效运行所必需的专用硬件:

  1. 在你的 Gradient 笔记本环境的根目录中打开终端:图 1.8– 在 Jupyter 笔记本中下拉打开终端

    图 1.8– 在 Jupyter 笔记本中下拉打开终端

  2. 在终端中,在笔记本的根目录创建一个新目录 fastai_cookbook

    mkdir fastai_cookbook
    
  3. 在终端中,将此新目录设置为当前目录:

    cd fastai_cookbook
    
  4. 在此新目录中初始化 git

    git init
    
  5. 克隆本书的仓库:

    git clone https://github.com/PacktPublishing/Deep-Learning-with-fastai-Cookbook.git
    
  6. 一旦仓库被克隆,进入 ch1 目录并打开 validate_gradient_setup.ipynb 笔记本:图 1.9 – Files 视图中的 validate_gradient_setup.ipynb 笔记本

    图 1.9 – Files 视图中的 validate_gradient_setup.ipynb 笔记本

  7. 运行整个笔记本(单元格 -> 全部运行)并检查输出。

  8. 对于第一个代码单元,如果你的笔记本能访问 fastai 库,你应该会看到类似下面的输出。无需担心 fastai 的具体版本——关键是你能够导入该库并获得有效的版本,而不会出现错误:图 1.10 – 获取 fastai 版本

    图 1.10 – 获取 fastai 版本

  9. 对于第二个代码单元,如果你的笔记本有访问 GPU 的权限,你应该会看到类似下面的表格。GPU 是一种专门用于深度学习的硬件,后续示例的高效运行将依赖于它。无需担心列出的具体 GPU 类型;只需确认输出中出现了类似的表格即可:

图 1.11 – nvidia-smi 命令的输出

图 1.11 – nvidia-smi 命令的输出

如果你从此单元获得如下输出,则说明你的笔记本未正确设置以访问 GPU:

图 1.12 – 来自 nvidia-smi 命令的错误

图 1.12 – 来自 nvidia-smi 命令的错误

恭喜!你已经设置好一个可以探索 fastai 的 Gradient 环境。

它是如何工作的…

现在你有了一个正常工作的 Gradient 实例,你将能够运行 fastai 示例。Gradient 包括 PyTorch、fastai 和其他运行本书示例所需的库,以及你需要的 GPU 硬件,能够高效地运行这些示例。

你需要注意的一些 Gradient 笔记本的方面列在这里:

  • 默认情况下,你的免费实例将运行 6 小时后自动关闭。如果你想要更长时间、不间断的会话,你需要更改为付费订阅。

  • 一般来说,重新启动一个 Gradient 实例需要大约 3 到 10 分钟,因此在你准备开始工作之前,最好提前几分钟进入 Paperspace 控制台的Notebook部分,然后点击START启动你的笔记本。我习惯先启动我的笔记本,然后去做其他事情(例如发邮件或泡一杯茶),这样就不会因为等待笔记本启动而浪费太多时间。

  • 如果你对如何使用 Jupyter 笔记本有些生疏,www.dataquest.io/blog/jupyter-notebook-tutorial/上的教程会很好地复习关键点。

还有更多……

如果你已经完成了本节中的所有步骤,并且拥有一个正常工作的 Gradient 环境,那么下一节不是必需的。我建议你同时设置 Gradient 和 Colab,但并不是必须同时拥有这两个环境才能完成本书中的大部分示例。不过,如果你想要两个环境的优点,你也可以为 fastai 设置 Colab——它也是免费的,而且相比 Gradient 有一些优势,比如支持 Keras 应用。

在 Google Colab 中设置 fastai 环境

如果你已经熟悉Google Colab环境,或者希望利用 Google 的整体机器学习生态系统,那么 Colab 可能是你用来探索 fastai 的合适环境。在本节中,我们将介绍如何设置 Colab 并验证它是否已准备好用于 fastai。

准备工作

要使用 Colab,你需要一个 Google ID 和访问 Google Drive 的权限。如果你还没有 Google ID,请按照这里的说明创建一个:support.google.com/accounts/answer/27441?hl=en

一旦你有了 Google ID,你需要确认你是否可以访问 Google Drive。你需要访问 Drive,因为它作为 Colab 的存储系统。当你在 Colab 中工作时,你会将笔记本和数据保存到 Drive 中。按照这里的说明获取 Drive 访问权限:support.google.com/drive/answer/2424384?co=GENIE.Platform%3DDesktop&hl=en

如何操作……

一旦你拥有一个可以访问 Drive 的 Google ID,就可以按照以下步骤设置 Colab 以与 fastai 一起使用。首先,我们将在 Colab 笔记本中获取 Drive 的访问权限,然后克隆本书的仓库,最后运行验证笔记本来确认设置是否成功。

  1. 打开 Colab (colab.research.google.com/)。

  2. 通过选择文件 -> 新建笔记本,打开一个新的空白笔记本。

  3. 在新的笔记本中,将以下语句粘贴到一个空白单元格中:

    print("hello world")
    

    然后,选择运行按钮:

    图 1.13 – Colab 运行按钮

    图 1.13 – Colab 运行按钮

  4. 确认你获得了预期的输出:图 1.14 – 在 Colab 中“hello world”预期输出

    图 1.14 – 在 Colab 中“hello world”预期输出

  5. 转到 Drive 并在 Drive 的根文件夹中创建一个名为fastai_cookbook的新文件夹。

  6. 进入这个新文件夹,右键点击,选择Google Colaboratory:图 1.15 – 在 Drive 中新目录中选择 Google Colaboratory

    图 1.15 – 在 Drive 中新目录中选择 Google Colaboratory

  7. Colab 将打开一个新的笔记本。在这个笔记本中,选择连接 -> 连接到托管运行时:图 1.16 – 选择连接到托管运行时

    图 1.16 – 选择连接到托管运行时

  8. 在这个笔记本的一个新单元格中,粘贴以下代码并运行单元格(例如,点击箭头):

    from google.colab import drive
    drive.mount('/content/drive')
    
  9. 在返回的响应中,点击提供的链接:图 1.17 – 提示在笔记本中挂载 Google Drive

    图 1.17 – 提示在笔记本中挂载 Google Drive

  10. 选择一个帐户:图 1.18 – 选择 Google 帐户的对话框

    图 1.18 – 选择 Google 帐户的对话框

  11. Google Drive 文件流访问屏幕上,选择允许:图 1.19 – Google Drive 文件流对话框

    图 1.19 – Google Drive 文件流对话框

  12. 登录屏幕上,选择复制图标以复制你的访问代码:图 1.20 – 获取访问代码的对话框

    图 1.20 – 获取访问代码的对话框

  13. 现在,返回 Colab 中的笔记本,并将访问代码粘贴到授权码字段中,然后按Enter键:图 1.21 – 输入访问代码以挂载 Google Drive

    图 1.21 – 输入访问代码以挂载 Google Drive

  14. 该单元格将运行并产生以下挂载消息,以确认你的 Google Drive 已经挂载,并且可以在 Colab 笔记本中使用:

图 1.22 – 确认 Google Drive 已挂载的消息

图 1.22 – 确认 Google Drive 已挂载的消息

现在 Drive 已在 Colab 中挂载,下一步是克隆本书的仓库:

  1. 通过在笔记本中运行以下命令,将 fastai_cookbook 新的目录文件夹设为当前目录:

    %cd /content/drive/MyDrive/fastai_cookbook
    
  2. 在新单元中运行以下命令来列出此目录的内容:

    %ls
    
  3. 在笔记本的新单元中运行以下代码来克隆书本的仓库:

    !git clone https://github.com/PacktPublishing/Deep-Learning-with-fastai-Cookbook.git 
    
  4. 再次运行该单元来列出目录内容,你应该会看到现在为仓库创建的目录。你可以在 Drive 中确认仓库已被克隆。

现在你已经克隆了仓库,可以运行验证笔记本来确认你已能访问 fastai 库和 GPU:

  1. 在 Drive 中,导航到 fastai_cookbook/Deep-Learning-with-fastai-Cookbook/ch1 文件夹,右键点击 validate_gradient_setup.ipynb 笔记本,并选择 使用方式 | Google Colaboratory

  2. 笔记本在 Colab 中打开。选择 运行时 | 更改运行时类型。在弹出的 笔记本设置 对话框中,选择 GPU 作为 硬件加速器,然后点击 保存图 1.23 – 在笔记本设置对话框中选择 GPU 作为硬件加速器

    图 1.23 – 在笔记本设置对话框中选择 GPU 作为硬件加速器

  3. 选择 运行时 | 全部运行 来运行笔记本。

  4. 确认你能像下面这样得到输出,没有错误,这是笔记本中的第一个代码单元的结果:图 1.24 – 确认 fastai 版本

    图 1.24 – 确认 fastai 版本

  5. 确认你能像下面这样得到输出,这是笔记本第二个代码单元的结果。不要担心列出的具体 GPU 类型——这会根据可用资源有所不同。如果你没有在 步骤 2 中指定 GPU 作为硬件加速器,那么你将不会得到这个输出:

图 1.25 – nvidia-smi 输出确认访问 GPU

图 1.25 – nvidia-smi 输出确认访问 GPU

恭喜!你已设置好一个可以探索 fastai 的 Colab 环境。

它是如何工作的…

现在你已拥有一个可用的 Colab 环境,你将能够在其中运行 fastai 示例。Colab 集成了 PyTorch、fastai 以及其他运行书中示例所需的库。请注意,不同于 Gradient,每次启动新的 Colab 会话时,你都需要按照步骤挂载 Drive,并指定你需要 GPU。默认情况下,Drive 并未挂载,你的 Colab 笔记本在没有明确更改硬件加速器类型之前无法访问 GPU。

还有更多…

如果你同时设置了 Gradient 和 Colab 环境,我建议你默认使用 Gradient 来运行本书中的示例。Gradient 让你可以直接访问终端,这对于输入命令行命令非常方便,而且每次启动新会话时无需挂载文件系统或请求 GPU。虽然 Colab 有一些优势,比如 6 小时后不会自动关闭,但总体而言,你在 Gradient 上的体验会更流畅。

在 Gradient 中设置 JupyterLab 环境

本章之前我们已经介绍了设置 Gradient 作为 fastai 探索环境的步骤。通过这个设置,你将获得标准的 Jupyter notebook 环境,其中包括文件系统视图,并能够更新笔记本、启动终端窗口,以及执行基本操作,如上传和下载本地系统中的文件。如果你希望拥有更丰富的开发环境,可以设置 Gradient 使用 JupyterLab。

除了让你在同一个浏览器标签页内维护多个视图(例如,终端视图和多个笔记本视图)外,JupyterLab 还允许你在笔记本环境中利用可视化调试器。在本节中,我们将介绍设置 Gradient 以便你可以使用 JupyterLab 的步骤。请注意,这个步骤是可选的——本书中任何可以在 Gradient 上运行的 JupyterLab 示例,也可以在普通 Jupyter 上运行。

准备工作

在你尝试设置 Gradient 与 JupyterLab 配合使用之前,确保你已经成功完成了 在 Paperspace Gradient 中设置 fastai 环境 部分的步骤。一旦你设置好 JupyterLab Gradient,你就可以随时在普通 Jupyter 视图和 JupyterLab 视图之间切换。

如何操作……

要设置 JupyterLab 环境,首先启动你的 Gradient 实例,运行一个命令来安装 JupyterLab,然后重新启动实例查看效果。以下是执行此操作的步骤:

  1. 启动你的 Gradient fastai 实例以打开普通 Jupyter Files 视图:图 1.29 – 普通 Jupyter 文件视图

    图 1.26 – 普通 Jupyter 文件视图

  2. 一旦你进入了实例的文件系统视图,选择 New | Terminal图 1.27 – 下拉菜单打开 Jupyter 终端

    图 1.27 – 下拉菜单打开 Jupyter 终端

  3. 这将打开一个终端窗口:图 1.28 – Jupyter 终端窗口

    图 1.28 – Jupyter 终端窗口

  4. 在终端窗口中,输入以下命令安装 JupyterLab:

    pip install jupyterlab
    
  5. 安装完成后,退出 Jupyter,在 Paperspace 控制台中停止你的 Gradient 实例,然后重新启动它。

  6. 当你在 URL 末尾看到带有 lab 的普通 Jupyter tree 时,按下 Enter。此时,你应该会看到 JupyterLab 视图,而不是普通 Jupyter 视图:

图 1.29 – Gradient 中的 JupyterLab 环境

图 1.29 – Gradient 中的 JupyterLab 环境

恭喜!你已经成功设置 Gradient,以便使用 JupyterLab 视图。

它是如何工作的……

你可以随时在原生 Jupyter 视图和 JupyterLab 之间来回切换,只需修改 URL,使其末尾是 tree(表示 Jupyter)或 lab(表示 JupyterLab)。

还有更多……

如果你想了解更多 JupyterLab 的好处,这篇教程会解释其功能以及如何使用它们:dzone.com/articles/getting-started-with-jupyterlab

我之前提到过 JupyterLab 的一个好处是它支持可视化的 Python 调试器,你可以在笔记本中使用它。有关此调试器以及如何设置的更多详细信息,请参阅 https://medium.com/@cristiansaavedra/visual-jupyter-debugger-for-python-e96fdd4f6f68blog.jupyter.org/a-visual-debugger-for-jupyter-914e61716559

"Hello world" for fastai – 为 MNIST 创建一个模型

现在你已经为 fastai 设置好了环境,接下来是运行一个示例。在这一节中,你将经历创建一个简单深度学习模型的过程,该模型在 MNIST 数据集上进行训练。这个数据集由手写数字的图像组成。训练后的模型目标是根据给定的图像预测数字。例如,我们希望训练的模型预测以下数字是 6396

图 1.30 – 来自 MNIST 数据集的手写数字样本

图 1.30 – 来自 MNIST 数据集的手写数字样本

在这一节中,我们不会涵盖 fastai 解决方案的每个细节,但我们将运行一个完整的示例,展示 fastai 的一个关键优势——仅用几行代码就能获得强大的深度学习结果。这个示例也应该能激发你对接下来几章中更高级 fastai 示例的兴趣。

准备工作……

请确保你已经按照步骤在 Gradient 中设置好 fastai,并确认你可以在 ch1 目录下打开 MNISThello_world 笔记本(mnist_hello_world.ipynb)。如果你选择使用 Colab,请确保你选择了 Runtime | Change runtime type,并选择了 GPU 作为硬件加速器。

本节使用的数据集是深度学习经典数据集 MNIST(yann.lecun.com/exdb/mnist/)。我非常感谢能使用这个数据集来提供 fastai 功能的初步演示。

数据集引用

Y. LeCun, L. Bottou, Y. Bengio 和 P. Haffner。(1998)基于梯度的学习应用于文档识别(yann.lecun.com/exdb/publis/pdf/lecun-98.pdf)。IEEE 会议录,86(11):2278-2324,1998 年 11 月

如何操作……

你将开始从头到尾运行整个笔记本。通过运行笔记本中的所有单元格,你将执行训练一个图像分类深度学习模型的代码,该模型预测给定手写数字图像属于哪个类别(即从 0 到 9 的哪个数字)。

首先,你将使 MNIST 数据集可用,该数据集包含一组按目录组织的手写数字图像(每个数字从 0 到 9 都有一个目录)。接下来,你将定义一个 dataloaders 对象,指定数据集的训练子集(即用于训练模型的图像)和验证子集(即用于评估模型训练过程中表现的图像)。接下来,你将使用 fastai 提供的预定义架构(即由多个层组成的模型组织方式)来定义深度学习模型本身。

接下来,你将训练模型,即迭代地应用训练集来更新模型中的权重,以优化模型在指定指标(在此模型中是准确率)上的表现。接下来,你将检查训练集和验证集中的图像批次。然后,你将查看模型分类表现最差的图像。最后,你将把训练好的深度学习模型应用于示例手写图像,看看模型是否能够预测这些图像的正确数字。接下来的步骤中,你将运行整个笔记本中的代码,然后逐个单元格查看代码的作用:

  1. 打开 ch1 目录下的 MNIST hello_world 笔记本 mnist_hello_world.ipynb

  2. 选择适合你环境的选项来运行整个笔记本:

    a) 单元格|全部运行(Jupyter)

    b) 运行|全部运行(JupyterLab)

    c) 运行时|全部运行(Colab)

  3. 确认笔记本能够正确运行到最后。你应该看到最后一个单元格的以下输出。如果看到不同的数字输出也不用担心,只要在这个单元格没有错误输出即可:

图 1.31 – 示例 MNIST 数字预测

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_01_31.jpg)

图 1.31 – 示例 MNIST 数字预测

恭喜!你刚刚成功地使用 fastai 训练了你的第一个深度学习模型,并使用训练好的模型预测 MNIST 数据集中的一组手写数字。接下来,我们将逐个单元格地回顾这个示例,了解它告诉我们关于 fastai 的信息:

  1. 第一个代码单元格导入笔记本所需的库:

    !pip install -Uqq fastbook
    import fastbook
    from fastbook import *
    from fastai.vision.all import *
    
  2. 第二个单元格调用 fastai 函数,准备运行 fastai 应用程序的笔记本。在 Colab 中,举例来说,这个函数会触发挂载 Drive 的步骤,使得 Drive 在笔记本中可用:

    fastbook.setup_book()
    
  3. 第三个单元格定义了将用于训练模型的数据集的位置。fastai 提供了一组现成的数据集(包括多个版本的 MNIST 数据集),你可以通过一次调用 untar_data() 函数将其导入到你的笔记本中。我们将在第二章《使用 fastai 探索和清理数据》中详细讨论这些数据集:

    path = untar_data(URLs.MNIST)
    
  4. 第五个单元格是笔记本的核心,展示了 fastai 的强大功能。以下是三行代码,完全定义并训练了一个深度学习模型:

    a) 第一行代码从前一个单元格创建的 path 对象生成一个 dataloaders 对象,并识别包含训练和验证数据集的子目录。有关 ImageDataLoaders 的更多详细信息,请参见 fastai 文档(docs.fast.ai/vision.data.html#ImageDataLoaders),dataloaders 对象的具体类型,专门用于图像问题:

    dls = ImageDataLoaders.from_folder(path, train='training', valid='testing')
    

    b) 第二行定义了深度学习模型的结构,包括其架构(基于著名的 1 轮(即,遍历整个训练集一次)和学习率将设置为 0.1):

    learn.fit_one_cycle(1, 0.1)
    
  5. 这个单元格的输出显示了训练过程的结果,包括训练和验证损失、验证准确率以及完成训练所花费的时间。请注意,准确率非常高:图 1.32 – MNIST 模型训练输出

    图 1.32 – MNIST 模型训练输出

  6. 接下来的两个单元格展示了训练和验证 测试 数据集的示例:图 1.33 – 来自 MNIST 训练和测试数据集的示例

    图 1.33 – 来自 MNIST 训练和测试数据集的示例

  7. 下一个单元格展示了模型预测错误最多的数字示例。注意,这些数字对于我们人类来说也不容易识别,所以模型预测错它们并不令人惊讶:图 1.34 – MNIST 模型最差预测的数字

    图 1.34 – MNIST 模型最差预测的数字

  8. 下一个单元格显示了关于模型的摘要信息,包括构成它的层、它的参数数量以及使用的优化器和损失函数:

    learn.summary()
    
  9. 最后,在笔记本的最后一组单元格中,显示来自验证集的数字图像,并应用训练好的模型对图像中的数字进行预测。在以下示例中,模型正确地从验证集中识别出零的图像为零:

图 1.35 – MNIST 模型的预测示例

图 1.35 – MNIST 模型的预测示例

就这样——一个完整的、自包含的深度学习模型,成功解决了一个著名的计算机视觉问题(预测手写图像中的数字),并且具有惊人的准确性。使用 fastai,你只需几行代码就能完成这项工作。这个笔记本包含一些额外的代码来验证模型并调查数据集,但你真正需要的只有前五个单元格,这些单元格加起来不到 10 行代码。

它是如何工作的……

你可能会问自己关于我们刚才讲解的代码的细节。数据摄取是如何工作的?什么是 dataloader?模型是如何知道包含summary()函数显示的所有层的?我们将在后续章节中回答这些问题。在第二章**,使用 fastai 探索和清理数据中,我们将深入探讨 fastai 的数据摄取过程,在后续章节中,我们将详细介绍 fastai 为一系列常见深度学习应用提供的解决方案,包括表格数据、文本数据、推荐系统和计算机视觉。

fastai 的一个优点是,你可以抽象掉深度学习的大部分复杂性(如果你愿意的话),并且仅用几行代码就能得到一个有效且有用的模型,就像我们刚才看到的 MNIST 模型一样。然而,fastai 并没有隐藏这些细节,也没有限制你的灵活性。除了提供一种用非常少的代码创建深度学习模型的优雅方式,fastai 还包含了一套层,每一层都揭示了更多的灵活性和细节。这意味着,随着你对 fastai 了解的深入,你可以继续挖掘并自定义你的解决方案,以满足你的具体需求。

还有更多……

本节使用了一些标准的机器学习术语,包括损失函数优化器准确率多类分类。如果你需要复习这些和其他基础机器学习概念,我推荐你参考这里的教程系列:machinelearningmastery.com/。这个网站包含了机器学习的主要概念的清晰描述,并且提供了 Python 代码示例,展示了如何应用这些概念。

通过四个应用理解世界:表格、文本、推荐系统和图像

在他们描述 fastai 的开创性论文中,Howard 和 Gugger(arxiv.org/pdf/2002.04688.pdf)描述了 fastai 支持的四个应用领域,这些领域是开箱即用的。在本节中,我们将介绍 fastai 直接支持的这四个深度学习应用:表格数据、文本数据、推荐系统和计算机视觉。你在上一节看到的 MNIST 示例就是计算机视觉应用的一个例子。MNIST 示例包括以下内容:

  • 精选数据集:MNIST。你可以在这里找到精选数据集的总体列表:

    course.fast.ai/datasets

  • 通过untar_data()轻松加载精心整理的数据集

  • 通过数据加载器对象对数据集进行图像特定处理

  • 通过Learner对象定义图像特定模型结构

  • 用于检查数据集的工具

同样,fastai 还提供了专门针对其他三个应用领域(表格数据、文本数据和推荐系统)的组件。在本节中,我们将逐一查看这些应用领域,并了解 fastai 如何为它们提供支持。

准备工作

保持你在上一节中完成的 MNIST 示例打开,因为我们将在回顾四个应用领域时引用它。在进入四个应用领域的描述之前,先定义一些术语:

  • DataLoader:一种结构,允许你访问批次的x(独立)值和y(依赖)值。x值是你用来训练模型的数据,而y值是你希望模型预测的内容。

  • DataLoaders:包含训练和验证DataLoader对象的结构。

  • Learner:一个结合了DataLoaders、架构和其他特征(包括损失函数和优化器)的对象,用来定义一个模型。为了与 Keras 中的模型进行对比,Learner对象完全包含了用于训练模型的数据,而在 Keras 模型中,数据集的各个方面(如训练中的独立值、依赖值等)是作为参数传递给模型,需要与模型本身分开处理。

如何实现…

让我们逐个回顾这四个应用领域,并检查 fastai 如何为它们提供支持。

  1. AG_NEWS(约 50 万篇分类新闻文章),以及DBPedia(来自知识库的训练/测试样本,wiki.dbpedia.org/about,包含来自 Wikimedia 项目的结构化内容),YELP_REVIEWS(约 150 万条 Yelp 评论及其相应的星级评分)

    b) 针对文本的DataLoaders对象:TextDataLoaders docs.fast.ai/text.data.html#TextDataLoaders

    c) 针对文本的学习器对象:TextLearner docs.fast.ai/text.learner.html#TextLearner

  2. DataLoaders对象:TabularDataLoaders docs.fast.ai/tabular.data.html#TabularDataLoaders

    b) 针对表格数据的Learner对象:TabularDataLearner docs.fast.ai/tabular.learner.html#TabularLearner

    c) 用于检查数据集的工具:TabularPandas docs.fast.ai/tabular.core.html#TabularPandas

  3. ML_SAMPLEML_100k(成千上万用户对成千上万电影的评分)

    b) 特定推荐系统的 DataLoaders 对象:CollabDataLoaders docs.fast.ai/collab.html#CollabDataLoaders

    c) 特定推荐系统的 Learner 对象:collab_learner docs.fast.ai/collab.html#Create-a-Learner

  4. BIWI_HEAD_POSE(人物图像及其位置描述)、PASCAL_2007PASCAL_2012(图像及每个图像的对应分割图)图像定位数据集

    b) 特定图像的 DataLoaders 对象:ImageDataLoaders docs.fast.ai/vision.data.html#ImageDataLoaders

    c) 特定图像的 Learner 对象:cnn_learner docs.fast.ai/vision.learner.html#cnn_learner

    d) 用于检查数据集的工具:许多便捷的函数,使得渲染单个图像和图像类别变得简单

它是如何工作的……

本节内容较多需要消化,但不用担心。fastai 的每个应用特定方面都有专门的章节,我们将详细介绍应用特定功能(数据集、DataLoadersLearners 等)并展示如何利用这些功能为各个应用领域创建深度学习解决方案。需要注意的重要一点是,fastai 提供这些应用特定功能,旨在让你能够轻松创建涉及以下四个领域的应用:表格数据文本数据推荐系统计算机视觉

使用 PyTorch 张量

本书的大部分内容将重点讲解 fastai 框架提供的功能。然而,我们回顾的一些解决方案也会利用一些通用的 Python 库(例如用于表格数据的深度学习应用中的 pandas 库),以及 fastai 构建所基于的低级深度学习框架 PyTorch 的一些方面。为了让你初步了解 PyTorch,本节将介绍一些使用张量的基础示例,张量是 PyTorch 用于表示多维矩阵的结构。

准备工作

如果你已经熟悉 NumPy 数组,那么你会有一个很好的基础来理解 PyTorch 张量,因为张量在 PyTorch 中的作用与 NumPy 数组在通用 Python 应用中的作用类似。如果你不熟悉 NumPy 数组,或者有一段时间没有使用过它们,建议花点时间复习一下它们,比如通过阅读这个教程:numpy.org/doc/stable/user/quickstart.html

完成 NumPy 数组回顾后,确保你已经按照步骤在 Gradient 中设置 fastai,并确认你可以打开 ch1 目录中的 PyTorch 张量演练笔记本 (pytorch_tensor_walkthrough.ipynb)。如果你选择使用 Colab,确保你已经选择了运行时 | 更改运行时类型并选择了GPU作为硬件加速器。

如何操作……

在本节中,我们将介绍一些基本的 PyTorch 张量操作:

  1. 打开 ch1 目录中的 pytorch_tensor_walkthrough.ipynb PyTorch 张量演练笔记本。

  2. 运行笔记本中的前四个单元格,以导入所需的库并定义三个张量。

    a) 请注意,由于本笔记本只使用了 PyTorch,并不需要任何 fastai 库,因此我们只需要一个import语句:

    import torch
    

    b) 定义a:一个二维的 5x7 张量,所有位置的值都是1

    图 1.36 – 定义一个 5x7 张量

    图 1.36 – 定义一个 5x7 张量

    c) 定义b:一个二维的 5x7 张量,除对角线上的1外,其他位置都为0

    图 1.37 – 定义一个对角线为 1 的 5x7 张量

    图 1.37 – 定义一个对角线为 1 的 5x7 张量

    d) 定义c:一个二维的 5x5 单位张量:

    图 1.38 – 定义一个 5x5 单位张量

    图 1.38 – 定义一个 5x5 单位张量

  3. 现在,运行张量b0行单元格:图 1.39 – 张量 b 的第 0 行

    图 1.39 – 张量 b 的第 0 行

    b) 获取0行的0元素:

    ![图 1.40 – 张量 b 的元素 [0,0]

    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_01_40.jpg)

    图 1.40 – 张量 b 的元素 [0,0]

    c) 获取从第 2 行开始到最后一行的所有行:

    图 1.41 – 张量 b 从某一行到最后一行

    图 1.41 – 张量 b 从某一行到最后一行

  4. 运行 ab 的单元格:

图 1.42 – 添加两个张量

图 1.42 – 添加两个张量

b) 尝试将张量ac相乘——请注意,你会遇到错误,因为这两个张量的维度不兼容。要乘以两个二维张量,第一个张量的第二维必须与第二个张量的第一维相同:

图 1.43 – 尝试乘以两个不兼容的张量会产生错误

图 1.43 – 尝试乘以两个不兼容的张量会产生错误

c) 定义一个 7x7 单位张量:

图 1.44 – 定义一个 7x7 单位张量

图 1.44 – 定义一个 7x7 单位张量

d) 现在,乘以张量ad——这次没有错误,因为这两个张量的维度是兼容的:

图 1.45 – 乘法运算两个兼容的张量

图 1.45 – 乘法运算两个兼容的张量

e) 创建一个新的张量,它是张量a的转置(即,张量a的列变为新张量的行):

图 1.46 – 转置张量

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_01_46.jpg)

图 1.46 – 转置张量

f) 计算张量a的转置与张量c的乘积——尽管张量a与张量c相乘时会导致错误,但这次不会发生错误,因为张量的维度是兼容的:

图 1.47 – 乘法两个兼容的张量

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_01_47.jpg)

图 1.47 – 乘法两个兼容的张量

恭喜!你已经初步体验了 PyTorch,这也是 fastai 的底层框架。

如何运作…

在本节中,你了解了张量,PyTorch 的基本构建块之一。如果你熟悉 Keras 与 TensorFlow 之间的关系,可以将 fastai 与 PyTorch 之间的关系视为类似。就像 Keras 是 TensorFlow 的高级应用程序编程接口API)一样,fastai 构建在 PyTorch 之上,并简化了 PyTorch 的一些复杂性(例如,通过对默认值做出合理的假设)。有了 fastai,你可以专注于创建深度学习应用,而无需担心所有细节。

还有更多内容…

如果你感到好奇并想了解 PyTorch 的概况,你可以查看这个入门教程:pytorch.org/tutorials/beginner/nlp/pytorch_tutorial.html

对比 fastai 与 Keras

在本节中,我们将讨论 fastai 与 Keras 之间的一些相似性和差异。虽然这两个框架都提供了深度学习的高级 API,但在架构、解决问题的方法以及使用这两个框架的社区之间,它们存在一些显著的差异。通过对比这两个框架,你将能更清晰地了解 fastai 的优势,为后续章节中对 fastai 应用的详细探讨做好准备。

准备开始

如果你最近使用过 Keras,那么你将能很好地从这一节中受益。如果你以前没有使用过 Keras,或者已经有一段时间没用过了,我建议你快速浏览一下这个教程,这样你就可以重新了解 Keras:keras.io/getting_started/intro_to_keras_for_engineers/

如何操作…

在本节中,我们将把 Keras 处理 MNIST 问题的方法与我们在本章前面回顾过的 fastai MNIST 解决方案进行对比。你可以在仓库的ch1目录下的keras_sequential_api_hello_world.ipynb文件中查看 Keras 的方法。

请注意,默认情况下,你无法在 fastai 的 Gradient 实例中运行这个 Keras 笔记本,因为所需的 TensorFlow 和 Keras 库在该实例中并未安装。如果你已经设置好了 Colab,你可以在 Colab 中运行 Keras 的 MNIST 笔记本。

  1. 比较库的import语句。两个 MNIST 示例需要的import语句数量相似:

    a) Keras:

    import tensorflow as tf
    import pydotplus
    from tensorflow.keras.utils import plot_model
    

    b) fastai:

    import fastbook
    from fastbook import *
    from fastai.vision.all import *
    
  2. 比较数据集的设置和定义:

    a) Keras—MNIST 数据集在 Keras 中是现成的。Keras 提供了七个这样的数据集—详情请参见keras.io/api/datasets/。相比之下,fastai 提供了超过 25 个这样的数据集。详情请参见course.fast.ai/datasets

    mnist = tf.keras.datasets.mnist
    (x_train, y_train), (x_test, y_test) = mnist.load_data()
    x_train, x_test = x_train / 255.0, x_test / 255.0
    

    b) fastai—这需要一个 Keras 不需要的setup语句(尽管这个setup语句在使用 Colab 时可以节省 Drive 挂载过程中的一步),但只需要两条语句来定义数据集,而 Keras 则需要三条语句:

    fastbook.setup_book()
    path = untar_data(URLs.MNIST)
    dls = ImageDataLoaders.from_folder(path, train='training', valid='testing')
    
  3. 比较模型定义语句:

    a) Keras—模型中的每一层都需要明确列出:

    # define layers for the hello world model
    hello_world_model = tf.keras.models.Sequential([ 
            tf.keras.layers.Flatten(input_shape=(28, 28)), 
            tf.keras.layers.Dense(128, activation='relu'), 
            tf.keras.layers.Dropout(0.15), 
            tf.keras.layers.Dense(10) 
    ])
    # compile the hello world model, including specifying the 
    loss # function, optimizer, and metrics
    hello_world_model.compile(optimizer='adam',                                                    
    sloss=tf.keras.losses.SparseCategoricalCrossentropy(from
    _logits=True), metrics=['accuracy'])
    

    b) fastai—一个语句定义整个模型。通过单一参数来指定架构(例如resnet18)使得模型定义更加简洁。需要注意的是,fastai 模型的架构与 Keras 模型的架构并不完全相同。例如,如果你将本笔记本中learn.summary()单元输出的层与 Keras 模型定义中的层进行比较,你会发现 fastai 模型比 Keras 模型有更多的层。总之,fastai 和 Keras 在 MNIST 问题上的解决方案并不是严格的对等比较

    learn = cnn_learner(dls, resnet18, pretrained=False,                                                                            
    sloss_func=LabelSmoothingCrossEntropy(), metrics=accuracy)
    
  4. 比较fit语句:

    a) Keras:

    history = hello_world_model.fit(x_train, y_train,
                                               batch_size=64,
                                               epochs=10,
                                               validation
    _split=0.15)
    

    b) fastai:

    learn.fit_one_cycle(1, 0.1)
    
  5. 比较 Keras 模型和 fastai 模型的性能。同样需要注意的是,由于模型之间存在差异(包括架构和拟合过程的细节),因此无法从两者在性能上的差异中得出一般性结论:

    a) Keras:

    Loss for test dataset: 0.07588852692145155
    Accuracy for test dataset: 0.9775
    

    b) fastai:

图 1.48 – 使用 fastai 训练 MNIST 模型的结果

图 1.48 – 使用 fastai 训练 MNIST 模型的结果

现在,你已经快速比较了 fastai 和 Keras 在相同 MNIST 问题上的表现。

它是如何工作的……

这个 Keras 解决方案和 fastai 解决方案在 MNIST 问题上的对比告诉了我们什么?

  • Keras 提供的现成数据集远少于 fastai,而定义这些数据集的 fastai 语句更简单。这是 fastai 的一个关键优势,特别是对于初学者来说。拥有多种可以轻松获取的数据集,对于学习深度学习非常有帮助。得益于 fastai 提供的大量且多样化的现成数据集,fastai 在这方面表现非常出色。在下一章,我们将花一些时间更详细地了解这些数据集。

  • 除了模型定义之外,在每个解决方案步骤中,Keras 和 fastai 之间的代码行数没有太大差别。这意味着对于 MNIST 问题,Keras 在提供一个完整解决方案时,并不比 fastai 的标准逊色多少,后者只需要几行代码。

  • 在 Keras 中,模型定义更为复杂,主要是因为 fastai 允许我们通过一个单一的架构参数定义构成模型的各个层,而在 Keras 中我们必须显式地定义每一层。尽管模型定义的复杂性增加,但 Keras 的可读性较好。因为在 Keras 中,层是显式列出的。相比之下,在高级的 fastai API 中,层并没有列出。

  • fastai 提供的可用性优于 Keras,因为它使用户可以使用高级 fastai API,而不必担心所有显式的细节。

  • 在 fastai 中,模型拟合的语句更简洁。此外,fastai 默认设置集成了最佳实践,这通常能带来更快的拟合时间和更好的性能。

Keras 由于显式列出了各个层,具有更高的透明性。而 fastai 则通过精心挑选的默认设置,提供了更好的可用性和开箱即用的性能。本书中我们不打算对 Keras 和 fastai 进行更多的比较,但根据我对 Keras 和 fastai 的使用经验,fastai 的优势在复杂应用中会更加突出。此外,fastai 还具有一个巨大的优势,那就是其拥有大量精心策划的现成可用数据集。

测试你的知识

现在你已经完成了本章的所有示例,接下来可以按照以下步骤来巩固你所学的知识:

  1. 复制 mnist_hello_world.ipynb 笔记本,并将其命名为 mnist_hello_world_variations.ipynb

  2. 更新你新复制的笔记本,使其能够加载一种 MNIST 数据集的变体,称为 MNIST_SAMPLE。为了加载这个数据集,你需要更新哪个语句,而不是使用完整的 MNIST 精选数据集?

  3. 使用 path.ls() 语句检查 MNIST_SAMPLE 数据集的目录结构。该语句的输出与完整 MNIST 数据集的输出有什么不同?

  4. 记住 MNIST_SAMPLE 数据集的目录结构差异,更新以下语句中的 trainvalid 参数值,以便它们能够与该数据集一起使用:

    dls = ImageDataLoaders.from_folder(path, train='training', valid='testing')
    
  5. 再次考虑目录结构的差异,更新以下语句,使其能够与 MNIST_SAMPLE 数据集一起使用:

    img_files = get_image_files(path/"testing")
    
  6. MNIST_SAMPLE 数据集比完整的 MNIST 数据集要小。记住这一点,请更新以下语句,以便它们能够与较小的数据集一起使用:

    img = PILImage.create(img_files[7000])
    img = PILImage.create(img_files[2030])
    img = PILImage.create(img_files[5800])
    
  7. 现在你已经更新了笔记本,使其能够与 MNIST_SAMPLE 数据集一起使用,运行整个笔记本来确认它能够顺利运行到最后,不会出现错误。

恭喜!如果你已经完成这一部分,那么你就成功地将一个食谱调整为适应另一个精选数据集。

第二章:第二章:使用 fastai 探索和清理数据

在上一章中,我们通过设置 fastai 框架的编程环境,完成一个具体的应用示例(MNIST),并调查了与 fastai 关系不同的两个框架:PyTorch 和 Keras,成功入门 fastai。在本章中,我们将深入探讨 fastai 的一个重要方面:获取探索清理数据。特别地,我们将探索 fastai 管理的一些数据集。

到本章结束时,你将能够描述 fastai 支持的完整数据集,使用 fastai 提供的工具查看这些数据集,并清理数据集以去除缺失值和非数字值。

本章将介绍以下的食谱:

  • 获取完整的 即用型 fastai 数据集

  • 使用 fastai 查看表格数据集

  • 使用 fastai 查看文本数据集

  • 使用 fastai 查看图像数据集

  • 使用 fastai 清理原始数据集

技术要求

确保你已完成第一章《与 fastai 开始》中的设置部分,并且拥有一个有效的 Gradient 实例或 Colab 环境。确保你已克隆了这本书的代码库(github.com/PacktPublishing/Deep-Learning-with-fastai-Cookbook),并且可以访问 ch2 文件夹。此文件夹包含本章将描述的代码示例。

获取完整的即用型 fastai 数据集

第一章《与 fastai 开始》中,你接触了 MNIST 数据集,并了解了如何轻松使该数据集可用于训练 fastai 深度学习模型。你能够训练该模型而不需要担心数据集的位置或结构(除了包含训练集和验证集的文件夹名称)。你还能够方便地查看数据集的元素。

在本节中,我们将更详细地了解 fastai 管理的完整数据集,并解释你如何获取关于这些数据集的更多信息。

准备工作

确保你已按照第一章《与 fastai 开始》中的步骤设置了 fastai 环境,并确认可以在克隆的代码库中的 ch2 目录下打开 fastai_dataset_walkthrough.ipynb 笔记本。

如何实现……

在本节中,你将通过运行 fastai_dataset_walkthrough.ipynb 笔记本以及 fastai 数据集文档,来了解 fastai 所管理的数据集。一旦你在 fastai 环境中打开了该笔记本,请按照以下步骤操作:

  1. 运行笔记本中的前三个单元,加载所需的库,设置 fastai 环境,并定义 MNIST 数据集:图 2.1 – 加载库、设置笔记本环境并定义 MNIST 数据集的单元

    图 2.1 – 加载库、设置笔记本环境并定义 MNIST 数据集的单元

  2. 考虑 untar_data 函数的参数:URLs.MINST。这是什么?让我们尝试 ?? 快捷方式查看 URLs 对象的源代码:图 2.2 –  的源代码

    图 2.2 – URLs 的源代码

  3. 通过查看 URLs 源代码中的 image classification datasets 部分,我们可以找到 URLs.MNIST 的定义:

    MNIST           = f'{S3_IMAGE}mnist_png.tgz'
    
  4. 通过逆向分析 URLs 类的源代码,我们可以得到 MNIST 的完整 URL:

    S3_IMAGE     = f'{S3}imageclas/'
    S3  = 'https://s3.amazonaws.com/fast-ai-'
    
  5. 综合来看,我们可以得到 URLs.MNIST 的 URL:

    https://s3.amazonaws.com/fast-ai-imageclas/mnist_png.tgz
    
  6. 你可以自己下载这个文件并解压。你会发现解压后的包的目录结构是这样的:

    mnist_png
    ├── testing
    │   ├── 0
    │   ├── 1
    │   ├── 2
    │   ├── 3
    │   ├── 4
    │   ├── 5
    │   ├── 6
    │   ├── 7
    │   ├── 8
    │   └── 9
    └── training
         ├── 0
         ├── 1
         ├── 2
         ├── 3
         ├── 4
         ├── 5
         ├── 6
         ├── 7
         ├── 8
         └── 9
    
  7. 在解压后的目录结构中,每个测试和训练目录下都有子目录,分别对应每个数字。这些数字目录包含该数字的图像文件。这意味着数据集的标签——我们希望模型预测的值——是通过图像文件所在的目录来编码的。

  8. 是否有办法获取某个精心策划的数据集的目录结构,而无需从 URLs 的定义中确定其 URL、下载数据集并解压?有的 – 使用 path.ls()图 2.3 – 使用 path.ls() 获取数据集的目录结构

    图 2.3 – 使用 path.ls() 获取数据集的目录结构

  9. 这告诉我们数据集包含两个子目录:trainingtesting。你可以调用 ls() 来获取 training 子目录的结构:图 2.4 – 训练子目录的结构

    图 2.4 – 训练子目录的结构

  10. 现在我们已经学会如何使用 ls() 函数获取 MNIST 数据集的目录结构,那么我们还能从 ??URLs 的输出中学到什么呢?

  11. 首先,让我们按组查看 ??URLs 输出中列出的其他数据集。首先,查看 main datasets 下列出的数据集。这个列表包括表格数据集(ADULT_SAMPLE)、文本数据集(IMDB_SAMPLE)、推荐系统数据集(ML_SAMPLE)以及各种图像数据集(CIFAR, IMAGENETTE, COCO_SAMPLE):

         ADULT_SAMPLE           = f'{URL}adult_sample.tgz'
         BIWI_SAMPLE            = f'{URL}biwi_sample.tgz'
         CIFAR                     = f'{URL}cifar10.tgz'
         COCO_SAMPLE            = f'{S3_COCO}coco_sample.tgz'
         COCO_TINY               = f'{S3_COCO}coco_tiny.tgz'
         HUMAN_NUMBERS         = f'{URL}human_numbers.tgz'
         IMDB                       = f'{S3_NLP}imdb.tgz'
         IMDB_SAMPLE            = f'{URL}imdb_sample.tgz'
         ML_SAMPLE               = f'{URL}movie_lens_sample.tgz'
         ML_100k                  = 'http://files.grouplens.org/datasets/movielens/ml-100k.zip'
         MNIST_SAMPLE           = f'{URL}mnist_sample.tgz'
         MNIST_TINY              = f'{URL}mnist_tiny.tgz'
         MNIST_VAR_SIZE_TINY = f'{S3_IMAGE}mnist_var_size_tiny.tgz'
         PLANET_SAMPLE         = f'{URL}planet_sample.tgz'
         PLANET_TINY            = f'{URL}planet_tiny.tgz'
         IMAGENETTE              = f'{S3_IMAGE}imagenette2.tgz'
         IMAGENETTE_160        = f'{S3_IMAGE}imagenette2-160.tgz'
         IMAGENETTE_320        = f'{S3_IMAGE}imagenette2-320.tgz'
         IMAGEWOOF               = f'{S3_IMAGE}imagewoof2.tgz'
         IMAGEWOOF_160         = f'{S3_IMAGE}imagewoof2-160.tgz'
         IMAGEWOOF_320         = f'{S3_IMAGE}imagewoof2-320.tgz'
         IMAGEWANG               = f'{S3_IMAGE}imagewang.tgz'
         IMAGEWANG_160         = f'{S3_IMAGE}imagewang-160.tgz'
         IMAGEWANG_320         = f'{S3_IMAGE}imagewang-320.tgz'
    
  12. 接下来,让我们看看其他类别的数据集:图像分类数据集、自然语言处理数据集、图像定位数据集、音频分类数据集和医学图像分类数据集。请注意,精心策划的数据集列表中包括一些与 fastai 支持的四个主要应用领域无关的数据集。例如,音频数据集应用于四个主要应用领域以外的使用场景:

         # image classification datasets
         CALTECH_101  = f'{S3_IMAGE}caltech_101.tgz'
         CARS            = f'{S3_IMAGE}stanford-cars.tgz'
         CIFAR_100     = f'{S3_IMAGE}cifar100.tgz'
         CUB_200_2011 = f'{S3_IMAGE}CUB_200_2011.tgz'
         FLOWERS        = f'{S3_IMAGE}oxford-102-flowers.tgz'
         FOOD            = f'{S3_IMAGE}food-101.tgz'
         MNIST           = f'{S3_IMAGE}mnist_png.tgz'
         PETS            = f'{S3_IMAGE}oxford-iiit-pet.tgz'
         # NLP datasets
         AG_NEWS                        = f'{S3_NLP}ag_news_csv.tgz'
         AMAZON_REVIEWS              = f'{S3_NLP}amazon_review_full_csv.tgz'
         AMAZON_REVIEWS_POLARITY = f'{S3_NLP}amazon_review_polarity_csv.tgz'
         DBPEDIA                        = f'{S3_NLP}dbpedia_csv.tgz'
         MT_ENG_FRA                    = f'{S3_NLP}giga-fren.tgz'
         SOGOU_NEWS                    = f'{S3_NLP}sogou_news_csv.tgz'
         WIKITEXT                       = f'{S3_NLP}wikitext-103.tgz'
         WIKITEXT_TINY               = f'{S3_NLP}wikitext-2.tgz'
         YAHOO_ANSWERS               = f'{S3_NLP}yahoo_answers_csv.tgz'
         YELP_REVIEWS                 = f'{S3_NLP}yelp_review_full_csv.tgz'
         YELP_REVIEWS_POLARITY   = f'{S3_NLP}yelp_review_polarity_csv.tgz'
         # Image localization datasets
         BIWI_HEAD_POSE      = f"{S3_IMAGELOC}biwi_head_pose.tgz"
         CAMVID                  = f'{S3_IMAGELOC}camvid.tgz'
         CAMVID_TINY           = f'{URL}camvid_tiny.tgz'
         LSUN_BEDROOMS        = f'{S3_IMAGE}bedroom.tgz'
         PASCAL_2007           = f'{S3_IMAGELOC}pascal_2007.tgz'
         PASCAL_2012           = f'{S3_IMAGELOC}pascal_2012.tgz'
         # Audio classification datasets
         MACAQUES               = 'https://storage.googleapis.com/ml-animal-sounds-datasets/macaques.zip'
         ZEBRA_FINCH           = 'https://storage.googleapis.com/ml-animal-sounds-datasets/zebra_finch.zip'
         # Medical Imaging datasets
         SIIM_SMALL            = f'{S3_IMAGELOC}siim_small.tgz'
    
  13. 现在我们已经列出了URLs中定义的所有数据集,我们如何了解更多信息呢?

    a) fastai 文档(course.fast.ai/datasets)记录了URLs中列出的一些数据集。请注意,这些文档与URLs源中的内容不完全一致。例如,数据集的命名不统一,且文档页面未涵盖所有数据集。如有疑问,请将URLs源视为你了解 fastai 策划数据集的唯一可信来源。

    b) 使用path.ls()函数来检查目录结构,如下例所示,它列出了 MNIST 数据集training子目录下的目录:

    图 2.5 – 训练子目录结构

    path = untar_data(URLs.PETS)
    

    e) 在这里,你可以找到storage/data/oxford-iiit-pet中的数据集,并查看目录结构:

    oxford-iiit-pet
    ├── annotations
    │   ├── trimaps
    │   └── xmls
    └── images
    
  14. 如果你想查看笔记本中某个函数的定义,可以运行一个包含??的单元格,后跟函数名。例如,要查看ls()函数的定义,你可以使用??Path.ls图 2.6 – Path.ls()的来源

    图 2.6 – Path.ls()的来源

  15. 若要查看任何函数的文档,你可以使用doc()函数。例如,doc(Path.ls)的输出会显示该函数的签名,并提供指向源代码(github.com/fastai/fastcore/blob/master/fastcore/xtras.py#L111)和文档(fastcore.fast.ai/xtras#Path.ls)的链接:

图 2.7 – doc(Path.ls)的输出

图 2.7 – doc(Path.ls)的输出

你现在已经浏览了 fastai 策划的即用型数据集列表。你还学会了如何获取这些数据集的目录结构,以及如何从笔记本中检查函数的源代码和文档。

它是如何工作的…

正如你在本节中看到的,fastai 为每个策划的数据集在URLs类中定义了相应的链接。当你用某个策划数据集作为参数调用untar_data时,如果该数据集的文件尚未被复制,这些文件会被下载到你的文件系统中(在 Gradient 实例中是storage/data)。你从untar_data获取的对象允许你查看数据集的目录结构,然后将其传递到创建 fastai 深度学习模型的下一阶段。通过这种便捷的方式包装大量有趣的数据集,fastai 使得你可以轻松地用这些数据集创建深度学习模型,并且可以将精力集中在创建和改进深度学习模型上,而不是在处理数据集导入的细节。

还有更多内容…

你可能会问,为什么我们要费力检查 URLs 类的源代码以获取有关精心策划数据集的详细信息。毕竟,这些数据集在 course.fast.ai/datasets 上有文档说明。问题是,这个文档页面没有提供所有精心策划数据集的完整列表,也没有清楚地解释你需要了解什么,以便为特定的精心策划数据集做出正确的 untar_data 调用。精心策划数据集的不完整文档展示了 fastai 的一个弱点——文档不一致。有时,文档是完整的,但有时也缺少细节,因此你需要直接查看源代码来弄清楚发生了什么,就像我们在这一节中为精心策划数据集所做的那样。这个问题还被 Google 搜索的结果所加剧,因为它返回的是早期版本 fastai 的文档。如果你正在搜索有关 fastai 的一些细节,避免进入 fastai 版本 1 的文档页面(fastai1.fast.ai/),并始终参考当前版本 fastai 的文档:docs.fast.ai/

使用 fastai 检查表格数据集

在上一节中,我们查看了 fastai 精心策划的所有数据集。在本节中,我们将深入分析精心策划数据集中一个表格数据集。我们将加载数据集,查看一些示例记录,然后探索数据集的特征,包括记录数和每列的唯一值数量。

准备工作

确保你已按照 第一章 的步骤,开始使用 fastai,并已设置好 fastai 环境。确认你能够在你的仓库的 ch2 目录中打开 examining_tabular_datasets.ipynb 笔记本。

我很感激有机会在本节中包含 ADULT_SAMPLE 数据集。

数据集引用

Ron Kohavi. (1996) 提升朴素贝叶斯分类器的准确性:决策树混合模型 (robotics.stanford.edu/~ronnyk/nbtree.pdf)。

如何操作……

在本节中,你将运行 examining_tabular_datasets.ipynb 笔记本来检查 ADULT_SAMPLE 数据集。

打开 fastai 环境中的笔记本后,完成以下步骤:

  1. 运行前两个单元格,导入必要的库并为 fastai 设置笔记本环境。

  2. 运行以下单元格,将数据集复制到你的文件系统中(如果尚未复制),并定义数据集的路径:

    path = untar_data(URLs.ADULT_SAMPLE)
    
  3. 运行以下单元格,以获取 path.ls() 的输出,查看数据集的目录结构:图 2.8 –  的输出

    图 2.8 – path.ls() 的输出

  4. 数据集位于 adult.csv 文件中。运行以下单元格,将此 CSV 文件加载到 pandas DataFrame 中:

    df = pd.read_csv(path/'adult.csv')
    
  5. 运行head()命令以获取数据集开头的记录样本:图 2.9 - 数据集开头的记录样本

    图 2.9 - 数据集开头的记录样本

  6. 运行以下命令以获取数据集中的记录数(行数)和字段数(列数):

    df.shape
    
  7. 运行以下命令以获取数据集中每一列的唯一值数量。你能从输出中看出哪些列是类别型的吗?

    df.nunique()
    
  8. 运行以下命令以获取数据集中每一列的缺失值数量。哪些列有缺失值?

    df.isnull().sum()
    
  9. 运行以下命令以显示数据集中年龄小于或等于 40 岁的人的部分样本记录:

    df_young = df[df.age <= 40]
    df_young.head()
    

恭喜!你已经摄取了 fastai 精心策划的表格数据集,并对数据集进行了基本的检查。

它是如何工作的……

你在本节中探索的数据集ADULT_SAMPLE,是你在前一节中看到的源中URLs部分列出的数据集之一。请注意,虽然URLs源识别了哪些数据集与图像或 NLP(文本)应用相关,但它没有明确指出哪些是表格数据集或推荐系统数据集。ADULT_SAMPLEmain datasets下列出的数据集之一:

图 2.10 - 来自 URLs 源的主要数据集

图 2.10 - 来自 URLs 源的主要数据集

我是如何确定ADULT_SAMPLE是一个表格数据集的呢?首先,Howard 和 Gugger 的论文(arxiv.org/pdf/2002.04688.pdf)将ADULT_SAMPLE标识为一个表格数据集。其次,我只需要摄取它并试一试,确认它能被摄取到 pandas DataFrame 中。

还有更多……

对于源中没有明确分类为URLs的其他精心策划的数据集怎么办?以下是源中URLsmain datasets部分列出的数据集总结:

  • 表格数据:

    a) ADULT_SAMPLE

  • 自然语言处理(文本):

    a) HUMAN_NUMBERS  

    b) IMDB

    c) IMDB_SAMPLE      

  • 协同过滤:

    a) ML_SAMPLE               

    b) ML_100k                              

  • 图像数据:

    a) URLsmain datasets下列出的所有其他数据集。

使用 fastai 检查文本数据集

在前一节中,我们看了如何摄取一个精心策划的表格数据集。在本节中,我们将深入探讨来自精心策划列表的文本数据集。

准备工作

确保你已经按照第一章中的步骤,快速入门 fastai,设置了 fastai 环境。确认你能在仓库的ch2目录中打开examining_text_datasets.ipynb笔记本。

我非常感激有机会使用本节中提到的 WIKITEXT_TINY 数据集(blog.einstein.ai/the-wikitext-long-term-dependency-language-modeling-dataset/)。

数据集引用

Stephen Merity, Caiming Xiong, James Bradbury, Richard Socher. (2016). 指针信号量混合模型 (arxiv.org/pdf/1609.07843.pdf)。

如何做...

在本节中,你将运行examining_text_datasets.ipynb笔记本,检查WIKITEXT_TINY数据集。顾名思义,这是一个从优秀的维基百科文章中提取的小型文本数据集。

一旦你在 fastai 环境中打开了这个笔记本,完成以下步骤:

  1. 运行前两个单元格来导入必要的库,并为 fastai 设置笔记本环境。

  2. 运行以下代码将数据集复制到你的文件系统中(如果它还没有在那里),并定义数据集的路径:

    path = untar_data(URLs.WIKITEXT_TINY)
    
  3. 运行以下代码获取path.ls()的输出,以便检查数据集的目录结构:图 2.11 – path.ls()的输出

    图 2.11 – path.ls()的输出

  4. 这个数据集由两个 CSV 文件组成。让我们将它们分别导入到 pandas 的 DataFrame 中,首先导入train.csv

    df_train = pd.read_csv(path/'train.csv')
    
  5. 当你使用head()检查 DataFrame 时,你会发现有些问题——CSV 文件没有列名的标题行,但默认情况下,read_csv会假设第一行是标题,所以第一行会被误解为标题。如以下截图所示,输出的第一行是粗体的,这表示第一行被解释为标题,尽管它实际上是普通的数据行:图 2.12 – df_train 中的第一条记录

    图 2.12 – df_train 中的第一条记录

  6. 为了解决这个问题,重新运行read_csv函数,但这次使用header=None参数,指定 CSV 文件没有标题行:

    df_train = pd.read_csv(path/'train.csv',header=None)
    
  7. 再次检查head(),确认问题已解决:图 2.13 – 修正 df_train 中的第一条记录

    图 2.13 – 修正 df_train 中的第一条记录

  8. 使用header=None参数将test.csv导入 DataFrame:

    df_test = pd.read_csv(path/'test.csv',header=None)
    
  9. 我们希望对数据集进行分词,并将其转换为单词列表。由于我们希望为整个数据集提供一个通用的词汇集,我们将从合并测试集和训练集的 DataFrame 开始:

    df_combined = pd.concat([df_train,df_test])
    
  10. 确认训练集、测试集和合并数据集的形状——合并后的 DataFrame 行数应该是训练集和测试集 DataFrame 行数的总和:

    print("df_train: ",df_train.shape)
    print("df_test: ",df_test.shape)
    print("df_combined: ",df_combined.shape)
    
  11. 现在,我们准备对 DataFrame 进行标记。tokenize_df()函数将包含我们要标记文本的列的列表作为参数。由于 DataFrame 的列没有标签,我们需要通过列的位置而不是列名来引用我们想要标记的列:

    df_tok, count = tokenize_df(df_combined,[df_combined.columns[0]])
    
  12. 检查df_tok的前几条记录,这是包含组合 DataFrame 标记内容的新 DataFrame:图 2.14 – 的前几条记录

    图 2.14 – df_tok的前几条记录

  13. 检查一些示例单词的数量,确保它们大致符合预期。选择一个非常常见的单词、一个中等常见的单词和一个罕见的单词:

    print("very common word (count['the']):", count['the'])
    print("moderately common word (count['prepared']):", count['prepared'])
    print("rare word (count['gaga']):", count['gaga'])
    

恭喜!您已成功加载、探索并标记了一个精选的文本数据集。

它是如何工作的…

您在本节中探索的数据集WIKITEXT_TINY是您在获取完整的即用型 fastai 数据集一节中看到的URLs来源中的数据集之一。在这里,您可以看到WIKITEXT_TINY位于URLs来源中的 NLP 数据集部分:

图 2.15 – 在来源中的 NLP 数据集列表中

图 2.15 – WIKITEXT_TINYURLs来源中的 NLP 数据集列表中

使用 fastai 检查图像数据集

在过去的两个部分中,我们检查了表格数据和文本数据,并了解了 fastai 提供的访问和探索这些数据集的功能。在本部分中,我们将查看图像数据。我们将查看两个数据集:FLOWERS图像分类数据集和BIWI_HEAD_POSE图像定位数据集。

准备就绪

确保您已经按照第一章,“快速入门 fastai”部分中的步骤设置好 fastai 环境。确认您可以在代码库的ch2目录中打开examining_image_datasets.ipynb笔记本。

我很感激有机会使用本节中展示的 FLOWERS 数据集。

数据集引用

Maria-Elena Nilsback, Andrew Zisserman. (2008). 自动化花卉分类(跨多个类别) (www.robots.ox.ac.uk/~vgg/publications/papers/nilsback08.pdf)。

我很感激有机会使用本节中展示的 BIWI_HEAD_POSE 数据集。

数据集引用

Gabriele Fanelli, Thibaut Weise, Juergen Gall, Luc Van Gool. (2011). 来自消费者深度相机的实时头部姿态估计 (link.springer.com/chapter/10.1007/978-3-642-23123-0_11)。计算机科学讲义,卷 6835,Springer,柏林,海德堡 doi.org/10.1007/978-3-642-23123-0_11

如何操作…

在本节中,您将运行examining_image_datasets.ipynb笔记本,以检查FLOWERSBIWI_HEAD_POSE数据集。

一旦您在 fastai 环境中打开笔记本,完成以下步骤:

  1. 运行前两个单元以导入必要的库并为 fastai 设置笔记本环境。

  2. 运行以下单元以将FLOWERS数据集复制到您的文件系统中(如果还没有的话),并为该数据集定义路径:

    path = untar_data(URLs.FLOWERS)
    
  3. 运行以下单元来获取path.ls()的输出,以便您可以检查数据集的目录结构:图 2.16 – 的输出

    图 2.16 – path.ls()的输出

  4. 查看valid.txt文件的内容。这表明train.txtvalid.txttest.txt包含属于这些数据集的图像文件列表:图 2.17 – 文件的前几个记录

    图 2.17 – valid.txt文件的前几个记录

  5. 查看jgp子目录:

    (path/'jpg').ls()
    
  6. 查看其中一个图像文件。请注意,get_image_files()函数不需要指向特定的子目录,它会递归地收集目录及其子目录中的所有图像文件:

    img_files = get_image_files(path)
    img = PILImage.create(img_files[100])
    img
    
  7. 您应该注意到,在上一步中显示的图像是图像的原始尺寸,这使得它对笔记本来说相当大。为了获取更合适大小的图像,可以应用to_thumb函数,并将图像的尺寸作为参数指定。请注意,当您运行此单元时,您可能会看到不同的图像:图 2.18 – 对图像应用 to_thumb

    图 2.18 – 对图像应用 to_thumb

  8. 现在,导入BIWI_HEAD_POSE数据集:

    path = untar_data(URLs.BIWI_HEAD_POSE)
    
  9. 查看此数据集的路径:

    path.ls()
    
  10. 查看05子目录:

    (path/"05").ls()
    
  11. 查看其中一张图像。请注意,您可能会看到不同的图像:图 2.19 – 数据集中的一张图像

    图 2.19 – BIWI_HEAD_POSE数据集中的一张图像

  12. 除了图像文件,此数据集还包括编码图像中所示姿势的文本文件。将其中一个文本文件导入到 pandas DataFrame 中并显示:

图 2.20 – 其中一个位置文本文件的前几个记录

图 2.20 – 其中一个位置文本文件的前几个记录

在本节中,您学习了如何导入两种不同类型的图像数据集,探索它们的目录结构,并检查数据集中的图像。

它是如何工作的…

你使用了相同的 untar_data() 函数来加载精心策划的表格、文本和图像数据集,并使用相同的 ls() 函数检查所有不同类型数据集的目录结构。除了这些通用功能,fastai 还提供了额外的便捷函数来检查图像数据:get_image_files() 用于收集从指定目录开始的目录树中所有的图像文件,to_thumb() 用于以适合笔记本的尺寸渲染图像。

还有更多…

除了图像分类数据集(训练模型的目标是预测图像中显示的类别)和图像定位数据集(目标是预测图像中给定特征的位置),fastai 精心策划的数据集还包括图像分割数据集,目标是识别图像中包含特定物体的子集,包括 CAMVIDCAMVID_TINY 数据集。

使用 fastai 清理原始数据集

现在我们已经探讨了 fastai 精心策划的各种数据集,本章还有一个主题需要介绍:如何使用 fastai 清理数据集。清理数据集包括处理缺失值和将类别值转换为数字标识符。我们需要对数据集应用这些清理步骤,因为深度学习模型只能使用数字数据进行训练。如果我们尝试用包含非数字数据的数据集(如缺失值和类别列中的字母数字标识符)来训练模型,训练过程将会失败。在本节中,我们将回顾 fastai 提供的工具,使清理数据集变得更加简单,从而使数据集准备好用于训练深度学习模型。

准备工作

确保你已按照 第一章 快速入门 fastai 中的步骤,设置好 fastai 环境。确认你能够在仓库的 ch2 目录下打开 cleaning_up_datasets.ipynb 笔记本。

如何做…

在这一部分中,你将通过运行 cleaning_up_datasets.ipynb 笔记本来处理 ADULT_SAMPLE 数据集中的缺失值,并将类别值替换为数字标识符。

一旦在 fastai 环境中打开笔记本,完成以下步骤:

  1. 运行前两个单元格,导入必要的库并为 fastai 设置笔记本环境。

  2. 回想一下本章的检查表格数据集与 fastai部分。当你检查 ADULT_SAMPLE 数据集中哪些列存在缺失值时,你会发现确实有些列包含缺失值。我们将识别 ADULT_SAMPLE 中缺失值所在的列,并利用 fastai 提供的功能,对这些列的缺失值进行处理,然后将这些类别值替换为数字标识符。

  3. 首先,我们再次获取 ADULT_SAMPLE 精心整理的数据集:

    path = untar_data(URLs.ADULT_SAMPLE)
    
  4. 现在,创建一个 pandas DataFrame 来存储数据集,并检查每列中的缺失值数量。注意哪些列有缺失值:

    df = pd.read_csv(path/'adult.csv')
    df.isnull().sum()
    
  5. 为了处理这些缺失值(并准备分类列),我们将使用 fastai 的 TabularPandas 类 (docs.fast.ai/tabular.core.html#TabularPandas)。使用此类时,我们需要准备以下参数:

    a) TabularPandas。在这里,我们将指定希望填充缺失值(FillMissing),并且我们将用数字标识符替换分类列中的值(Categorify)。

    b) ADULT_SAMPLE,其中因变量是 salary

    c) cont_cat_split() (docs.fast.ai/tabular.core.html#cont_cat_split) 函数,用于自动识别连续列和分类列:

    procs = [FillMissing,Categorify]
    dep_var = 'salary'
    cont,cat = cont_cat_split(df, 1, dep_var=dep_var)
    
  6. 现在,使用这些参数创建一个名为 df_no_missingTabularPandas 对象。这个对象将包含填补了缺失值的数据集,并且分类列中的值已经被替换为数字标识符:

    df_no_missing = TabularPandas(df, procs, cat, cont, y_names = dep_var)
    
  7. 使用 show API 显示 df_no_missing 的内容示例。请注意,当使用 show() 显示该对象时,分类列中的值得以保留。那么,如何将分类值替换为数字标识符呢?别担心——我们将在下一步中看到这个结果:图 2.21 –  中的前几条记录,分类列中为数字标识符

    图 2.21 – df_no_missing 中的前几条记录

  8. 现在,使用 items.head() API 显示 df_no_missing 的一些示例内容。这时,分类列中包含的是数字标识符,而非原始值。这是 fastai 提供的一个好处:它优雅地处理了原始分类值与数字标识符之间的切换。如果你需要查看原始值,可以使用 show() API,它会将分类列中的数字值转换回原始值,而 items.head() API 显示的是分类列中的实际数字标识符:图 2.22 –  中前几条记录,分类列中为数字标识符

    图 2.22 – df_no_missing 中前几条记录,分类列中为数字标识符

  9. 最后,让我们确认缺失值是否已正确处理。正如你所看到的,原本有缺失值的两个列在 df_no_missing 中已经没有缺失值:

图 2.23 –  中的缺失值

图 2.23 – df_no_missing 中的缺失值

通过这些步骤,你已经看到 fastai 如何轻松准备数据集来训练深度学习模型。它通过填补缺失值并将分类列中的值转换为数字标识符来实现这一点。

它是如何工作的…

在这一节中,你看到了一些 fastai 如何简化常见数据准备步骤的方法。TabularPandas类通过简化执行准备表格数据集的常见步骤(包括替换缺失值和处理类别列)提供了很大的价值。cont_cat_split()函数会自动识别数据集中的连续和类别列。总的来说,fastai 使得数据清理过程变得更容易,且比手动编写所有所需函数来完成这些数据集清理步骤时出错的概率更低。

第三章:第三章:使用表格数据训练模型

在上一章中,我们学习了如何使用 fastai 导入各种类型的数据集,以及如何清理数据集。在本章中,我们将深入了解如何使用 fastai 和表格数据训练模型。表格数据是指以行和列组织的数据,通常可以在电子表格文件或数据库表中找到,它对大多数企业至关重要。fastai 框架通过提供一整套功能来支持基于表格数据的深度学习应用,充分认识到了表格数据的重要性。

为了使用 fastai 探索表格数据的深度学习,我们将回到 ADULT_SAMPLE 数据集,这是我们在第二章《使用 fastai 探索和清理数据》中检查的其中一个数据集。通过使用这个数据集,我们将训练一个深度学习模型,同时学习 TabularDataLoaders(用于定义训练和测试数据集)和 tabular_learner(用于定义和训练模型)对象。

我们还将研究 curated 数据集以外的其他数据集,学习如何将非 curated 数据集导入到 fastai 中,以便训练深度学习模型。我们将通过探索什么使得表格数据集成为训练 fastai 深度学习模型的良好候选者,并学习如何保存已训练的模型来结束本章内容。

本章将涵盖以下食谱:

  • 使用 curated 表格数据集在 fastai 中训练模型

  • 使用非 curated 表格数据集在 fastai 中训练模型

  • 使用独立数据集训练模型

  • 评估一个表格数据集是否适合 fastai

  • 保存已训练的表格模型

  • 测试你的知识

技术要求

确保你已经完成了第一章《使用 fastai 入门》中的设置部分,并且有一个可用的 Gradient 实例或 Colab 设置。确保你已经克隆了本书的代码仓库(github.com/PacktPublishing/Deep-Learning-with-fastai-Cookbook),并且可以访问 ch3 文件夹。该文件夹包含本章描述的代码示例。

使用 curated 表格数据集在 fastai 中训练模型

第二章《使用 fastai 探索和清理数据》中,你学习了如何导入和检查 ADULT_SAMPLE curated 表格数据集。在这个食谱中,我们将详细介绍如何使用 fastai 在该数据集上训练一个深度学习模型。这将为你提供使用 fastai 创建表格深度学习模型的顺畅路径的概述。本食谱的目标是使用这个数据集训练一个深度学习模型,预测特定记录中描述的人员的薪资是否会高于或低于 50k。

准备工作

确认你能在你的代码库的ch3目录下打开training_with_tabular_datasets.ipynb笔记本。

我感谢能有机会在本节中包含ADULT_SAMPLE数据集。

数据集引用

Ron Kohavi. (1996) 提高朴素贝叶斯分类器的准确性:决策树混合方法 (robotics.stanford.edu/~ronnyk/nbtree.pdf)。

如何操作……

在这个食谱中,你将会运行training_with_tabular_datasets.ipynb笔记本。打开笔记本并在 fastai 环境中后,完成以下步骤:

  1. 运行笔记本中的单元格,直到定义转换因变量连续和分类列单元格。运行这些单元格后,你将设置好笔记本,并将ADULT_SAMPLE策划的表格数据集导入到 pandas DataFrame 中,之后会在本笔记本的其他部分使用它们。这些单元格与在第二章中展示的笔记本相同,使用 fastai 探索和清理数据,用于检查策划的表格数据集examining_tabular_datasets.ipynb

  2. 使用以下代码运行笔记本中的第一个新单元格:

    procs = [FillMissing,Categorify]
    dep_var = 'salary'
    cont,cat = cont_cat_split(df, 1, dep_var=dep_var)
    

    这个单元格设置了以下值:

    a) procs:这是将应用于TabularDataLoaders对象的转换列表。FillMissing表示将列中的缺失值替换为该列的中位数值。Categorify表示将分类列中的值替换为数字标识符。

    b) dep_var:用于识别数据集中哪个列包含因变量。这个列也被称为目标值或模型的 y 值——它是训练好的模型将要预测的值。在这个模型中,我们预测的是salary列的值。

    c) contcat:这是从df DataFrame 中分别返回的连续列和分类列的列表,这些列是由cont_cat_split函数返回的。这个函数是 fastai 为表格深度学习模型提供的一个主要优势。它通过自动检测哪些列是连续的(即可以取无限集合的值,如货币金额、物理尺寸或物体的计数)或分类的(即只能取有限的离散值,如美国的州或一周的天数),从而节省了大量的重复编码。

  3. 使用以下代码运行下一个单元格,以定义名为dlsTabularDataLoaders对象。请注意,在定义该对象时指定的一些参数(如批次大小)通常与训练过程相关,而不是定义训练数据集:

    dls=TabularDataLoaders.from_df(
    df, path, procs= procs,
    cat_names= cat, cont_names = cont, 
    y_names = dep_var, 
    valid_idx=list(range(1024,1260)), bs=64)
    

    TabularDataLoaders对象的定义使用以下参数:

    a) df, path, procs:包含已处理数据集的 DataFrame、数据集的路径对象,以及在前一步中定义的转换列表。

    b) cat_names, cont_names:在前一步中定义的分类列和连续列的列表。

    c) y_names:包含因变量/目标值的列。

    d) valid_idxdf DataFrame 中将被保留作为训练过程验证数据集的行子集的索引值。

    e) bs:训练过程中的批量大小。

  4. 运行以下代码单元格来定义并训练模型。第一行指定使用 TabularDataLoaders 对象定义模型,该对象在前一步中已定义,并使用准确度作为训练过程中优化的度量标准。第二行触发训练过程,进行三次迭代(即三轮遍历整个数据集):

    learn = tabular_learner(dls,layers=[200,100], metrics=accuracy)
    learn.fit_one_cycle(3)
    

    该单元格的输出将是按轮次显示的训练结果。结果包括每个轮次的编号、训练损失、验证损失以及每轮的耗时:

    图 3.1 – 训练结果

    图 3.1 – 训练结果

  5. 运行以下单元格以获取训练模型预测的示例结果。你可以将 salary 列与 salary_pred 列(模型预测值,在前面的截图中突出显示)进行比较,从而获得模型在这一数据集样本中表现的概览。在这个样本集中,模型的预测与 salary 列中因变量的实际值相匹配:图 3.2 – show_results 的输出

    图 3.2 – show_results 的输出

  6. 运行以下单元格以获取训练模型结构的总结:

    learn.summary()
    

    该单元格的输出包括以下细节:

    a) 构成模型的所有层的列表:

图 3.3 – 构成模型的层列表

图 3.3 – 构成模型的层列表

b) 训练模型中的参数、优化器和损失函数,以及使用的回调函数。回调函数(docs.fast.ai/callback.core.html)指定在训练过程中执行的操作,如在执行所有迭代之前停止训练过程。对于这个训练好的模型,回调函数由 fastai 自动指定,以跟踪已完成的训练轮次(TrainEvalCallback)、按批次/轮次跟踪损失和度量(Recorder)以及在训练过程中显示进度条(ProgressCallBack):

图 3.4 – summary() 输出中的附加细节

图 3.4 – summary() 输出中的附加细节

你现在已经使用 fastai 完全训练了一个深度学习模型,并使用了精心整理的表格数据集。

它是如何工作的…

正如你在这个食谱中看到的那样,一旦数据被导入,只需几行 fastai 代码就能得到一个训练好的深度学习模型。fastai 代码的简洁性和紧凑性,部分归功于它在可能的情况下做出合理的默认选择。

例如,fastai 确定,当目标列是分类数据时,模型应预测某一类别中的选项(在这个模型中,是预测一个人的工资是否高于或低于 50k,而不是连续的数值)。以下是 fastai 在使用表格数据训练深度学习模型时提供的额外好处:

  • 检测 DataFrame 中哪些列是分类数据,哪些列是连续数据

  • 选择适当的回调函数来进行训练过程

  • 定义深度学习模型的层(包括为类别列定义的嵌入层)

让我们比较一下在 Keras 深度学习模型中需要做些什么。在 Keras 深度学习模型中,对于一个表格数据集,模型的每个特征都必须明确编码,这将导致创建模型的代码更长、更复杂。此外,学习表格数据集深度学习模型的人还需要处理更多细节,才能得到第一个有效的训练模型。总的来说,fastai 使得快速构建基本深度学习模型成为可能。

在继续下一个食谱之前,值得深入探讨一下这个食谱中的模型,因为本章其他食谱将遵循相同的模式。对该模型的详细描述超出了本书的范围,因此我们这里只关注一些亮点。

如食谱中所示,模型被定义为 tabular_learner 对象(文档链接:docs.fast.ai/tabular.learner.html)。这个对象是 fastai learner 对象的一个特化版本,后者你首次见到是在 第一章 了解四大应用领域:表格、文本、推荐系统和图像 部分中。在 learn.summary() 的输出中,你可以看到这个模型的结构。输出的开始部分指定了 Input shape: ['64 x 9', '64 x 6'] —— 第一个元素的第二维度对应着类别列的数量,第二个元素的第二维度对应着连续列的数量。为类别列(cat 列表中的列)定义了嵌入层(文档链接:docs.fast.ai/layers.html#Embeddings),并为连续列定义了一个 BatchNorm 层(文档链接:docs.fast.ai/layers.html#BatchNorm-layers)。

要查看模型结构的更多详细信息,请查看learn.model的输出。特别地,你可以看到,在最后一个Linear层中,out_features = 2,这对应着模型的二元输出(个人收入是否高于/低于 50k)。

在 fastai 中使用非精心整理的表格数据集训练模型

第二章《使用 fastai 探索和清理数据》中,你回顾了 fastai 提供的精心整理的数据集。在之前的食谱中,你创建了一个深度学习模型,该模型已经在这些精心整理的数据集之一上进行了训练。如果你想为一个非精心整理的数据集训练 fastai 模型,该怎么办呢?

在本食谱中,我们将介绍如何处理一个非精心整理的数据集——Kaggle 房价数据集(www.kaggle.com/c/house-prices-advanced-regression-techniques/data)并在其上训练深度学习模型。这个数据集提出了一些额外的挑战。与精心整理的 fastai 数据集相比,处理该数据集需要额外的步骤,而且它的结构需要特别处理,以应对缺失值问题。

本食谱的目标是使用该数据集训练一个深度学习模型,预测房屋的销售价格是高于还是低于该数据集的平均价格。

准备就绪

要完成本食谱,你需要一个 Kaggle ID。如果你还没有,可以在这里注册:www.kaggle.com/account/login。获得 Kaggle ID 后,请按照以下步骤获取在笔记本中访问 Kaggle 房价数据集的令牌:

  1. 使用你的 Kaggle ID 登录,点击账户(右上角),然后点击账户图 3.5 – Kaggle 账户菜单

    图 3.5 – Kaggle 账户菜单

  2. kaggle.json文件将被下载到你的本地系统:图 3.6 – 选择创建新 API 密钥来获取新的 Kaggle API 密钥

    图 3.6 – 选择创建新 API 密钥来获取新的 Kaggle API 密钥

  3. 在你的 Gradient 环境中,打开一个终端会话,进入/root目录,并创建一个名为.kaggle的新目录。

  4. 将你在步骤 2中下载的kaggle.json文件上传到你刚刚创建的新目录中,也就是/root/.kaggle

这些步骤将帮助你准备好在本食谱中使用 Kaggle 房价数据集(www.kaggle.com/c/house-prices-advanced-regression-techniques/data)。

我很感激能有机会在这一部分包含房价数据集。

数据集引用

Dean De Cock (2011)。艾姆斯,爱荷华州:作为学期末回归项目的波士顿住房数据的替代方案 (jse.amstat.org/v19n3/decock.pdf) 《统计学教育期刊》第 19 卷,第 3 期(2011 年),(www.amstat.org/publications/jse/v19n3/decock.pdf)

如何操作…

在此示例中,你将通过 accessing_non_curated_datasets.ipynb 笔记本以及 fastai 数据集文档来理解 fastai 所整理的数据集。一旦你在 fastai 环境中打开了该笔记本,完成以下步骤:

  1. 如果你还没有安装,运行以下命令以安装 kaggle 库:

    pip install kaggle
    
  2. 运行笔记本的前三个单元格,加载你在此示例中所需的库,并准备好 fastai 笔记本。

  3. 在你的 Gradient 环境中,进入 /root/.kaggle 目录并打开 kaggle.json 文件。该文件的内容应如下所示,其中你的 ID 和 32 字符的密钥分别是第一个和第二个值:

    {"username":<YOUR ID>,"key":<YOUR KEY>}
    
  4. 复制你的 kaggle.json 文件的内容。

  5. 在你复制的 accessing_non_curated_datasets.ipynb 笔记本中,将 kaggle.json 文件的内容粘贴到单引号中,以将值分配给变量 creds。然后,运行此单元格:

    creds = '{"username":<YOUR ID>,"key":<YOUR KEY>}'
    
  6. 运行此单元格以设置数据集的凭证路径:

    cred_path = Path('~/.kaggle/kaggle.json').expanduser()
    
  7. 运行此单元格以设置数据集的路径:

    path = URLs.path('house_price')
    
  8. 运行此单元格以创建目标目录以存储数据集,下载数据集,将数据集解压到目标目录,并列出目标目录的内容:

    if not path.exists():
        path.mkdir()
        api.competition_download_cli('house-prices-advanced-regression-techniques', path=path)
        file_extract(path/'house-prices-advanced-regression-techniques.zip')
        path.ls(file_type='text')
    
  9. path.ls() 函数的输出显示了数据集的结构。在此示例中,我们将使用 train.csv 来训练深度学习模型,然后使用 test.csv 来测试训练后的模型:

    [Path('/storage/archive/house_price/sample_submission.csv'),
    Path('/storage/archive/house_price/data_description.txt'),
    Path('/storage/archive/house_price/train.csv'),
    Path('/storage/archive/house_price/test.csv')]
    
  10. 运行此单元格以将 train.csv 文件加载到名为 df_train 的 pandas DataFrame 中:

    df_train = pd.read_csv(path/'train.csv')
    
  11. 运行此单元格以将 test.csv 文件加载到名为 df_test 的 pandas DataFrame 中:

    df_test = pd.read_csv(path/'test.csv')
    
  12. 运行 shape 命令获取 df_traindf_test 的维度。注意到 df_testdf_train 少了一列——你能想到为什么会这样吗?哪个列在 df_test 中缺失?

  13. 运行此单元格以定义 under_over() 函数,如果输入值小于均值,则返回 '0',否则返回 '1'

    def under_over(x,mean_x):
        if (x <= mean_x):
            returner = '0'
        else:
            returner = '1'
        return(returner)
    
  14. 运行此单元格使用 under_over() 函数,该函数将用指示值是否高于或低于该列平均值的标志替换 SalePrice 列中的值:

    mean_sp = int(df_train['SalePrice'].mean())
    df_train['SalePrice'] = df_train['SalePrice'].apply(lambda x: under_over(x,mean_sp))
    df_train.head()
    
  15. 当你显示 df_train DataFrame 的内容时,你会看到 SalePrice 列中的值已被替换为零和一:图 3.7 –  列中的值已被替换

    图 3.7 – SalePrice 列中的值已被替换

  16. 运行此单元格以查看 SalePrice 列中新值的计数:

    df_train['SalePrice'].value_counts()
    
  17. 运行此单元格以定义应用于数据集的变换,包含因变量(目标)的列,以及连续和分类列:

    dep_var = 'SalePrice'
    cont,cat = cont_cat_split(df_train, 1, dep_var=dep_var)
    

    此单元格设置以下值:

    a) procs:这是一个将应用于TabularDataLoaders对象的变换列表。FillMissing指定将列中的缺失值替换为该列的中位数值。Categorify指定将分类列中的值替换为数字标识符。

    b) dep_var:用于标识数据集中包含因变量的列,即包含我们希望模型预测的值的列。对于此模型,我们预测的是SalePrice列的值。

    c) contcat:这些是使用cont_cat_splitdf_train DataFrame 返回的连续和分类列的列表。

  18. 运行此单元格以检查df_train DataFrame 中的缺失值。第一行获取 DataFrame 中缺失值的计数,而第二行定义了一个新的 DataFrame,df_train_missing,它包含原始 DataFrame 中每一列中至少有一个缺失值的行。此 DataFrame 的列包括包含缺失值的列的名称、每列的缺失值计数以及每列中缺失值的比例:

    count = df_train.isna().sum()
    df_train_missing = (pd.concat([count.rename('missing_count'),count.div(len(df_train)).rename('missing_ratio')],axis = 1).loc[count.ne(0)])
    

    查看df_train_missing中的值,我们可以看到某些列有大量的缺失值:

    图 3.8 – 来自 df_train_missing 的行

    图 3.8 – 来自 df_train_missing 的行

  19. 运行此单元格以处理df_traindf_test DataFrame 中的缺失值。前两条语句将分类列中的缺失值替换为最常见的非缺失值,而后两条语句将连续列中的缺失值替换为零:

    df_train[cat] = df_train[cat].fillna(df_train[cat].mode().iloc[0])
    df_test[cat] = df_test[cat].fillna(df_test[cat].mode().iloc[0])
    df_train[cont] = df_train[cont].fillna(0.0)
    df_test[cont] = df_test[cont].fillna(0.0)
    
  20. 运行此单元格以再次检查df_train DataFrame 中的缺失值:

    count = df_train.isna().sum()
    df_train_missing = (pd.concat([count.rename('missing_count'),count.div(len(df_train)).rename('missing_ratio')],axis = 1).loc[count.ne(0)])
    
  21. 现在,当你检查df_train_missing DataFrame 的内容时,它将是空的,确认所有缺失值已经处理完毕:图 3.9 – 确认缺失值已经处理

    图 3.9 – 确认缺失值已经处理

  22. 运行此单元格以创建TabularDataLoaders对象。第一行定义了将在TabularDataLoaders对象中应用的变换程序,第二行定义了TabularDataLoaders对象:

    procs = [Categorify, Normalize]
    dls_house=TabularDataLoaders.from_df(
        df_train,path,procs= procs,
        cat_names= cat, cont_names = cont, y_names = dep_var, 
        valid_idx=list(range((df_train.shape[0]-100),df_train.shape[0])), 
        bs=64)
    

    以下是TabularDataLoaders对象定义的参数:

    a) procs:这是一个将应用于TabularDataLoaders对象的变换列表。Normalize指定所有值都会被缩放到一致的范围。Categorify指定将分类列中的值替换为数字标识符。

    b) df_train, path, procs:包含导入数据集的 DataFrame,数据集的路径对象,以及在前一步中定义的转换列表。

    c) cat_names, cont_names:类别和连续列的列表。

    d) y_names:包含因变量/目标值的列。

    e) valid_idx:将保留为训练过程验证数据集的df DataFrame 中行子集的索引值。

    f) bs:训练过程的批次大小。

  23. 运行此单元格来定义并训练深度学习模型。第一行指定模型是使用我们在前一步中定义的TabularDataLoaders对象创建的,并且使用默认的层数和准确度作为训练过程中优化的指标。第二行触发了5个 epoch 的训练过程:

    learn = tabular_learner(dls_house, layers=[200,100], metrics=accuracy)
    learn.fit_one_cycle(5)
    

    训练过程会产生一个输出,显示每个 epoch 的训练损失、验证损失和准确度。这意味着我们已经训练出了一个可以预测验证集中的某个属性成本是否高于或低于平均值的模型,准确率为 92%。你能想到一些原因,为什么这个模型的准确率比你在前一节中训练的模型要好一些吗?

    图 3.10 – 在非精选数据集上训练的结果

    图 3.10 – 在非精选数据集上训练的结果

  24. 你可以运行此单元格来将训练好的模型应用于测试数据集。请注意,由于该数据集的结构,测试数据集不包含 y 相关的值,这意味着虽然你可以将模型应用于测试记录,但你无法评估模型在测试集上做出的预测的准确性:

    dl = learn.dls.test_dl(df_test)
    
  25. 运行此单元格以获取训练模型在测试数据集上做出的预测样本:

    learn.show_results()
    

    learn.show_results()的输出让你可以查看将训练好的模型应用于数据集的结果:

图 3.11 – 将训练好的模型应用于非精选数据集后的结果子集

图 3.11 – 将训练好的模型应用于非精选数据集后的结果子集

你现在已经完成了使用非精选数据集来训练 fastai 深度学习模型的过程。

它是如何工作的……

在本节中,你学习了如何适应另一种类型的数据集,其中训练集和测试集是分开的。在这种情况下,你不能依赖TabularDataLoaders对象中的转换来处理缺失数据。因此,本节中的代码单独处理了训练集和测试集中的缺失值。

使用独立数据集训练模型

在本章的前几个食谱中,我们介绍了如何在一个经过整理的表格数据集和一个直接从 Kaggle 加载的数据集上训练 fastai 模型。在这个食谱中,我们将探讨如何用来自自立文件的数据集来训练模型。我们将在这个食谱中使用的数据集由马来西亚吉隆坡的房地产列表组成,并可通过 Kaggle 网站上的www.kaggle.com/dragonduck/property-listings-in-kuala-lumpur获取。

这个数据集与我们之前见过的表格数据集不同。我们之前遇到的数据集行为良好,仅需要少量清理工作。相比之下,吉隆坡的房地产数据集是一个现实世界的数据集。除了缺失值外,它还包含许多错误和不规则性。它的规模也足够大(超过 5 万条记录),为深度学习提供了一个合适的应用场景。

准备工作

确保你已经按照第一章中的步骤操作,快速入门 fastai,以便设置好 fastai 环境。确认你能够在你的仓库的ch3目录下打开training_model_standalone_tabular_dataset.ipynb笔记本。还需确保按照以下步骤上传了数据文件:

  1. www.kaggle.com/dragonduck/property-listings-in-kuala-lumpur下载data_kaggle.csv.zip

  2. 解压下载的文件以提取data_kaggle.csv

  3. 在你的 Gradient 环境中的终端,从/storage/archive设置为当前目录:

    cd /storage/archive
    
  4. 创建一个名为/storage/archive/kl_property的文件夹:

    mkdir kl_property
    
  5. data_kaggle.csv上传到/storage/archive/kl_property。你可以使用 Gradient 中的 JupyterLab 上传按钮进行上传,但需要按照以下步骤操作:

    a) 在你的 Gradient 环境中的终端,将/notebooks设置为当前目录:

    /notebooks/temp:
    
    

    临时打开你的当前文件夹,选择上传按钮,如下图所示,并从你解压文件的本地系统文件夹中选择data_kaggle.csv文件:

    
    

图 3.12 – JupyterLab 中的上传按钮

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_03_12.jpg)

图 3.12 – JupyterLab 中的上传按钮

d) 在你的 Gradient 环境中的终端,将data_kaggle.csv复制到/storage/archive/kl_property

cp /notebooks/temp/data_kaggle.csv /storage/archive/kl_property/data_kaggle.csv

我想在此感谢本节中使用的数据集,并感谢能够将其纳入本书的机会。

数据集引用

Jas S (2019)。 吉隆坡房地产列表 (www.kaggle.com/dragonduck/property-listings-in-kuala-lumpur)

让我们看看下一节该如何进行。

如何操作…

在本教程中,你将通过运行training_model_standalone_tabular_dataset.ipynb笔记本,使用吉隆坡房地产价格数据集来训练模型。

打开 fastai 环境中的笔记本后,按照以下步骤操作:

  1. 运行前三个单元格以导入必要的库,并为 fastai 设置笔记本。

  2. 运行此单元格,将path与您复制了data_kaggle.csv文件的目录关联起来:

    path = URLs.path('kl_property')
    
  3. 运行此单元格以将数据集导入到df_train数据框中:

    df_train = pd.read_csv(path/'data_kaggle.csv')
    
  4. 运行以下单元格查看数据集的前几行:

    df_train.head()
    

    df_train.head()的输出显示了数据集中的一些数据行:

    图 3.13 – 吉隆坡房地产数据集中的一部分数据行

    图 3.13 – 吉隆坡房地产数据集中的一部分数据行

  5. 注意查看Price列中的值。本教程的目标是训练一个深度学习模型,预测此列中的值是高于还是低于平均值。为此,我们需要从此列中的数值开始。仅从这小段数据样本中,你可以看到三个问题:

    a) 这些值包括RM,这是马来西亚货币林吉特的符号。

    b) 这些值包括千分位的逗号分隔符。

    c) 某些行在此列中有缺失值。

    在接下来的几个单元格中,我们将处理这些问题,确保该列最终包含完全有效的数值。

  6. 运行此单元格以获取数据集中的行数。输出的第一个元素是数据框中的行数:

    df_train.shape
    
  7. 运行单元格以定义remove_currency()remove_after_space()函数。你将需要这些函数来清理数据集。

  8. 运行此单元格以解决Price列中由df_train.head()输出显示的问题。此单元格将删除数据集中Price值缺失的行(第一条语句),去除Price列中的货币符号(第二条语句),并在去除逗号后将Price列中的值转换为数值:

    df_train.dropna(subset=['Price'], inplace=True)
    df_train['Price'] = df_train['Price'].apply(lambda x: remove_currency("RM ",x))
    df_train['Price'] = pd.to_numeric(df_train['Price'].str.replace(',',''), errors='coerce')
    
  9. 运行此单元格再次获取数据集中的行数。了解这个数字很重要,因为我们已经在上一个单元格中删除了一些行,并且在清理Size列时还会删除更多行。通过检查删除行前后数据框的形状,我们可以确保不会丢失过多的信息:图 3.14 – 删除行之前获取数据框的形状

    图 3.14 – 删除行之前获取数据框的形状

  10. Size列包含了许多有用的信息,但我们必须做一些工作,才能将其准备好帮助训练深度学习模型。首先,运行以下单元格以查看Size列中一些示例值。我们希望从此列中提取以下信息:

    a) 将前缀(例如,Built-upLand area等)提取到新列中。

    b) 获取Size列剩余内容的单一数值;即,将 1,335 sq. ft.替换为 1,335,将 22 x 80 sq. ft.替换为 1,760。

    c) 删除Size列中后缀无法转换为数值的行。

    df_train['Size'].head()的输出显示了Size列中值的示例:

    图 3.15 – 列中值的示例

    图 3.15 – Size列中值的示例

    以下是我们希望对Size列进行的操作,以便为训练深度学习模型做准备:

    a) 将前缀(例如Built-upLand area等)提取到一个新列中。

    b) 在可能的情况下,获取Size列剩余内容的单一数值。例如,我们希望将 1,335 sq. ft.替换为 1,335,将 22 x 80 sq. ft.替换为 1,760。

    c) 对于无法从Size列剩余内容中提取数值的行,删除该行。

  11. 运行单元格以定义clean_up_size()函数。你将使用此函数对Size列执行以下清理步骤。结果将是一个 DataFrame,Size列中的所有值都将是代表物业面积的数值。以下是clean_up_size()函数执行的一些转换:

    a) 将Size列中的所有值转为小写。

    b) 将Size列拆分为一个新列(Size_type),该列包含非数值信息,剩余的Size列则包含有关物业面积的数值信息。

    c) 用0替换新Size列中的缺失值。

    d) 删除不包含任何数字的行。

    e) 删除包含问题子串的行,如clean_up_list中列出的内容。

    f) 替换多余的字符,使所有Size条目都为数值型或numerica * numericb的形式。

    g) 将numerica * numericb形式的值替换为两者的乘积;即,numericanumericb

  12. 运行此单元格以执行clean_up_size()函数:

    clean_up_list = ["-","\+",'\'','\~',"xx","sf","acre","#"]
    df_train = clean_up_size(df_train,clean_up_list)
    
  13. 再次运行shape命令,以获取删除了不包含足够数据的行后的 DataFrame 形状。这确认了在删除无效行后,我们丢失了约 2%的行:图 3.16 – 清理后 DataFrame 的形状

    图 3.16 – 清理后 DataFrame 的形状

  14. 运行df_train.head()查看清理步骤后的 DataFrame 样本。注意,现在我们得到以下结果:

    a) 添加了新的Size_type列。

    b) Size列包含数值型数据。

    df_train.head()的输出显示了PriceSize列清理后的 DataFrame 样子:

    图 3.17 – 对价格和面积列执行清理步骤后的 DataFrame

    图 3.17 – 执行完 Price 和 Size 列清理步骤后的 DataFrame

  15. 运行该单元格以定义under_over()函数。你将运行该函数,以将Price列中的值替换为指示价格是否高于或低于平均价格的标记。

  16. 运行以下单元格以将Price列中的值替换为指示价格高于或低于平均值的标记:

    mean_sp = int(df_train['Price'].mean())
    if categorical_target:
        df_train['Price'] = df_train['Price'].apply(lambda x: under_over(x,mean_sp))
    

    运行该单元格后,Price列中的值将被替换:

    图 3.18 – 替换了价格值为高于/低于平均值指示的 DataFrame

    图 3.18 – 替换了价格值为高于/低于平均值指示的 DataFrame

  17. 运行该单元格以定义将应用于TabularDataLoaders对象的转换、目标列(Price)以及连续和类别列列表:

    procs = [FillMissing,Categorify]
    dep_var = 'Price'
    cont,cat = cont_cat_split(df_train, 1, dep_var=dep_var)
    
  18. 运行该单元格以定义TabularDataLoaders对象,使用你在笔记本中定义的参数,包括数据集(df_train)、将应用于数据集的转换列表(procs)、连续和类别列列表(contcat)以及因变量(dep_var):

    dls = TabularDataLoaders.from_df(df_train,path,procs= procs, 
             cat_names= cat, cont_names = cont, 
             y_names = dep_var,
             valid_idx=list(range((df_train.shape[0]-5000),df_train.shape[0])), 
             bs=64)
    
  19. 运行该单元格以拟合模型并查看模型的性能。你的准确率和损失值可能会有所不同,但你应该能看到超过 90%的准确率,对于一个训练数据少于 10 万条记录的模型来说,这已经很好了:图 3.19 – 模型拟合结果

    图 3.19 – 模型拟合结果

  20. 运行该单元格以查看验证集中的结果。你将看到,在这组结果中,模型正确预测了一个属性的价格是否高于或低于平均价格:

图 3.20 – 模型预测属性价格是否高于或低于平均值

图 3.20 – 模型预测属性价格是否高于或低于平均值

恭喜你!你已经在一个需要进行不小清理的数据集上,使用 fastai 训练了一个深度学习模型。

它是如何工作的……

在本教程中,你看到虽然 fastai 提供了便捷工具来训练深度学习模型,以处理表格数据集,但你仍需确保数据集能够训练模型。这意味着需要从数值列中去除非数值值。这可能需要迭代清理步骤,就像你在本教程的笔记本中所看到的那样。

本教程中的数据集包含了典型的现实世界数据集中的异常和不一致,因此你在本教程中使用的技术(包括通过 pandas 操作去除某些列中有问题值的行,以及字符串替换技术)将适用于其他现实世界的数据集。

即使数据集杂乱,得益于 fastai 自动选择智能默认值并自动化关键操作(如识别分类和连续列、处理缺失值),它也能轻松帮助你获得高性能的深度学习模型。

评估表格数据集是否适合 fastai

到目前为止,我们已经使用 fastai 为表格数据集创建了三个深度学习模型。但如果你想确定一个新数据集是否适合使用 fastai 训练深度学习模型呢?在本节中,我们将介绍如何评估一个数据集是否适合使用 fastai 进行深度学习。

准备就绪

确保你已按照第一章的步骤,快速入门 fastai,设置好 fastai 环境。

如何操作…

正如你在本章中所看到的,你可以选择多种数据集进行深度学习应用。为了评估一个数据集是否适合,我们将通过从头开始创建一个新的笔记本并从在线 API 获取数据的过程。请按照以下步骤操作:

  1. 在 Gradient 中创建一个新笔记本。你可以通过以下步骤在 Gradient JupyterLab 中完成此操作:

    a) 点击主窗口中的新启动器按钮(+):

    图 3.21 – 在 JupyterLab 中打开新启动器

    图 3.21 – 在 JupyterLab 中获取新启动器

    b) 当启动器窗格打开时,点击 Python 3 来打开一个新的笔记本:

    图 3.22 – JupyterLab 中的启动器窗格

    图 3.22 – JupyterLab 中的启动器窗格

  2. 在新笔记本中,创建并运行两个新的单元格,设置笔记本所需的语句:图 3.23 – 为表格数据集设置 fastai 笔记本的单元格

    图 3.23 – 为表格数据集设置 fastai 笔记本的单元格

  3. 运行以下单元格以导入调查此数据集所需的附加库:

    ! pip install pandas_datareader
    import numpy as np
    import pandas as pd
    import os
    import yaml
    # For reading stock data
    from pandas_datareader.data import DataReader
    # For time stamps
    from datetime import datetime
    
  4. 运行以下单元格以加载 AstraZeneca(股票代码 = AZN)公司的股票价格数据集:

    df = DataReader('AZN', 'stooq')
    
  5. 检查 df.head() 的输出,查看数据框的内容:图 3.24 – 股票价格数据集示例

    图 3.24 – 股票价格数据集示例

  6. 获取数据框 df 的形状,以确定数据框中有多少行。你认为这个数据集足够大,能够成功训练一个深度学习模型吗?图 3.25 – 获取 df 的形状

    图 3.25 – 获取 df 的形状

  7. 运行以下单元格以准备检查数据框是否有缺失值

    count = df.isna().sum()
    df_missing = (pd.concat([count.rename('missing_count'),count.div(len(df)).rename('missing_ratio')],axis = 1).loc[count.ne(0)])
    
  8. 现在确认数据集没有缺失值!图 3.26 – 确认数据集没有缺失值

    图 3.26 – 确认数据集没有缺失值

  9. 运行以下单元格以设置训练运行的参数:

    dep_var = 'Close'
    # define columns that are continuous / categorical
    cont,cat = cont_cat_split(df, 1, dep_var=dep_var)
    
  10. 运行以下单元格来定义 TabularDataLoaders 对象:

    procs = [Normalize]
    dls = TabularDataLoaders.from_df(df,procs= procs,
            cat_names= cat, cont_names = cont, 
            y_names = dep_var, 
            valid_idx=list(range((df.shape[0]-50),df.shape[0])), bs=64)
    
  11. 运行以下单元格来定义并训练模型:

    learn = tabular_learner(dls, metrics=accuracy)
    learn.fit_one_cycle(30)
    
  12. 你可以从以下输出看到模型的表现很差:图 3.27 – 训练模型时的较差表现

    图 3.27 – 训练模型时的较差表现

  13. 现在,我们想进行一些更改,尝试获得一个表现更好的模型。首先,运行以下单元格来定义一个函数,创建一个新的目标列:

    def get_target(value,threshold):
        '''return based on whether the input value is greater than or less than input threshold'''
        if value <= threshold:
            return_value = "0"
        else:
            return_value = "1"
        return(return_value)
    
  14. 运行以下单元格来定义新的目标列:

    threshold = df['Close'].mean()
    df['target'] = df['Close'].apply(lambda x: get_target(x,threshold))
    
  15. 运行以下单元格来指定新的target列为因变量,并限制cont,即用于训练模型的连续列集合。注意,在本节的第一个模型中,因变量是Close,一个连续列。这意味着第一个模型是在尝试预测一个连续值。由于target是一个分类列,新的模型将预测一个分类值而不是连续值:

    dep_var = 'target'
    cont = ['High', 'Low', 'Open', 'Volume']
    
  16. 运行以下单元格来使用新的因变量和新的连续列集合训练一个新模型:

    dls = TabularDataLoaders.from_df(df,procs= procs, 
        cat_names= cat, cont_names = cont, 
        y_names = dep_var, 
        valid_idx=list(range((df.shape[0]-50),df.shape[0])), 
        bs=64)
    learn = tabular_learner(dls, metrics=accuracy)
    learn.fit_one_cycle(30)
    
  17. 你可以看到这个新模型比之前的模型有更好的表现:

图 3.28 – 第二个模型的表现提升

图 3.28 – 第二个模型的表现提升

在这个配方中,你尝试了两种深度学习模型变体,用于预测股价信息数据集。第一个模型的因变量是连续的,并且在整个过程中使用了 fastai 的默认设置。与本章中的其他配方不同,第一个模型的表现较差。如果你尝试使用更多的训练轮次来训练第一个模型,你会发现性能并没有改善。第二个模型将因变量从连续变量改为分类变量,表现要好得多。

它是如何工作的……

本节的第一个模型并未成功。在使用 1.3 千条记录训练深度学习模型来预测连续值时,很可能无法成功。一般来说,你需要一个大一个数量级的训练集,至少是数十万条甚至数百万条记录,才能预测一个连续的结果。

保存训练好的表格模型

到目前为止,我们已经在表格数据集上训练了一系列 fastai 深度学习模型。这些模型在我们训练模型的 Python 会话中是可用的,但是我们该如何保存这些模型,以便在不同的会话中再次使用呢?在这个配方中,我们将学习如何将一个 fastai 深度学习模型保存到文件,并在另一个 Python 会话中访问该模型。

准备工作

确保您已经按照第一章的步骤,快速入门 fastai,来设置 fastai 环境。确认您可以在存储库的ch3目录中打开saving_models_trained_with_tabular_datasets.ipynbloading_saved_models_trained_with_tabular_datasets.ipynb笔记本。

如何操作…

在这个操作步骤中,您将通过运行saving_models_trained_with_tabular_datasets.ipynb笔记本来训练一个模型——这个模型与本章第一个操作步骤中训练的模型相同——并将其保存。然后,您将使用loading_saved_models_trained_with_tabular_datasets.ipynb笔记本加载并执行已保存的 fastai 模型。

一旦您在 fastai 环境中打开了saving_models_trained_with_tabular_datasets.ipynb笔记本,按照以下步骤操作:

  1. 运行笔记本中的单元格,直到保存训练好的模型单元格。通过运行这些单元格,您将把ADULT_SAMPLE整理好的表格数据集导入到 pandas DataFrame 中,并在其上训练一个 fastai 模型。

  2. 运行接下来的两个单元格,将模型的路径值设置为可写目录。确保您设置的 learn.path 指向的目录存在并且是可写的。

  3. 运行此单元格,将训练好的模型保存到adult_sample_model.pkl文件中:

    learn.export('adult_sample_model.pkl')
    
  4. 现在您已经将训练好的模型保存到文件中,您必须将其加载到另一个笔记本中,以测试您在新的 Python 会话中加载模型的过程,然后使用保存的模型对测试数据进行预测。

  5. 在 Gradient 会话中打开loading_saved_models_trained_with_tabular_datasets.ipynb笔记本。

  6. 运行单元格,直到加载已保存的训练模型单元格,以加载所需的库并设置笔记本。

  7. 运行此单元格,将您之前在本操作步骤中保存的模型加载到这个新笔记本中。确保您指定了之前保存模型的路径:

    learn = load_learner('/notebooks/temp/models/adult_sample_model.pkl')
    
  8. 运行此单元格,将测试数据集加载到 DataFrame 中:

    df_test = pd.read_csv('adult_sample_test.csv')
    
  9. 运行此单元格,选择测试数据集的第一行,并应用训练好的模型来获取该数据点的预测:

    test_sample = df_test.iloc[0]
    learn.predict(test_sample)
    
  10. 结果包括模型对该数据点的预测。您可以看到,对于这个数据点,模型预测的salary值为1.0,这意味着它预测该个体的工资将超过 50k:

图 3.29 – 应用保存的模型对测试数据点进行预测的结果

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_03_29.jpg)

图 3.29 – 应用保存的模型对测试数据点进行预测的结果

恭喜!您已经成功保存了一个 fastai 深度学习模型,加载了保存的模型到新笔记本中,并应用该模型对一行测试数据进行预测。

其工作原理…

fastai 框架包括支持,使用learner对象的export()方法将深度学习模型保存到文件系统中。在这个配方中,你看到了如何将训练后的模型保存到 pickle 文件的示例。你还学会了如何将 pickle 文件加载回 Python,并将训练后的模型应用于新的数据示例。这是对在部署的模型上进行推理的过程的提前预览。在第七章《部署与模型维护》中,你将看到在部署的模型上进行推理的完整示例。

测试你的知识

现在你已经完成了本章中的配方,按照这里展示的步骤来实践你所学的内容。你将通过调整你在本章中操作过的一个笔记本,使其能够与新数据集配合使用。

准备工作

按照以下步骤上传新的表格数据集:

  1. 访问 Kaggle 未来销售预测比赛的站点(www.kaggle.com/c/competitive-data-science-predict-future-sales/data),并接受比赛条件,以便访问与比赛相关的数据集。

  2. 下载sales_train.csv.ziptest.csv.zip文件。

  3. 解压下载的文件以提取sales_train.csvtest.csv

  4. 在 Gradient 环境中的终端中,将当前目录设置为/storage/archive

    cd /storage/archive
    
  5. 创建一个名为/storage/archive/price_prediction的文件夹:

    mkdir price_prediction
    
  6. sales_train.csvtest.csv上传到/storage/archive/price_prediction。你可以使用 Gradient 中的 JupyterLab 上传按钮,通过你在本章前面创建的/notebooks/temp目录来上传:

    a) 在 JupyterLab 文件浏览器中,将temp设置为当前文件夹,选择上传按钮,如下图所示,然后从本地系统文件夹中选择sales_train.csvtest.csv,该文件夹是你在第 2 步中提取它们的地方:

图 3.30 – JupyterLab 中的上传按钮

图 3.30 – JupyterLab 中的上传按钮

b) 在 Gradient 环境中的终端中,将/storage/archive/price_prediction设置为当前目录:

cd /storage/archive/price_prediction

c) 将sales_train.csvtest.csvdata_kaggle.csv复制到/storage/archive/price_prediction

cp /notebooks/temp/sales_train.csv  sales_train.csv 
cp /notebooks/temp/test.csv  test.csv

现在你已经上传了数据集,是时候创建一个笔记本来导入数据集了:

  1. 创建一个你在使用独立数据集训练模型配方中操作过的training_model_standalone_tabular_dataset.ipynb笔记本的副本。将副本命名为training_model_new_tabular_dataset.ipynb

  2. 在你的新笔记本中,更新用于导入训练数据集的单元格:

    path = URLs.path('price_prediction') 
    df_train = pd.read_csv(path/'sales_train.csv')
    
  3. df_train.head() 的输出应该展示数据集的结构。你可以在 www.kaggle.com/c/competitive-data-science-predict-future-sales/data?select=sales_train.csv 找到该数据集的列描述:

图 3.31 – sales_train.csv 内容的一个样本

图 3.31 – sales_train.csv 内容的一个样本

恭喜!你已经将另一个独立的表格数据集导入到 fastai 笔记本中。你可以应用类似的技术将其他表格数据集导入,以便在 fastai 解决方案中使用。

现在你已经导入了数据集,考虑一下你将采取哪些步骤来准备该数据集用于训练深度学习模型。你会如何处理任何缺失值?你是否可以对某些列应用测试,来检测和修正错误值?Kaggle 比赛预测每个产品和商店的下个月总销售量。完成一个 fastai 模型来解决这个问题超出了本书的范围,但请考虑如何重构数据集,以便为这个问题做好准备。

第四章:第四章:使用文本数据训练模型

第三章使用表格数据训练模型中,你通过一系列实例展示了如何利用 fastai 的功能在表格数据上训练深度学习模型。在本章中,我们将研究如何利用 fastai 框架在文本数据集上训练深度学习模型。

为了在 fastai 中探索文本数据的深度学习,我们将从一个预训练的语言模型开始(即给定一个短语,预测接下来会出现哪些词),并使用 IMDb 精心挑选的数据集对其进行微调。接着,我们将使用微调后的语言模型创建一个文本分类器模型,用于电影评论用例,这个用例由 IMDb 数据集表示。该文本分类器用于预测一个短语的类别;在电影评论用例中,它预测给定短语是积极的还是消极的

最后,我们将相同的方法应用于一个独立的(即非精心挑选的)关于 Covid 相关推文的文本数据集。首先,我们将在 Covid 推文数据集上对现有的语言模型进行微调。然后,我们将使用微调后的语言模型训练一个文本分类器,该分类器根据 Covid 推文数据集定义的类别来预测短语的类别:极其消极消极中立积极极其积极

本章中使用的在文本数据集上训练深度学习模型的方法,也叫ULMFiT,最初由 fastai 的创始人在他们的论文《通用语言模型微调用于文本分类arxiv.org/abs/1801.06146中描述。该方法将迁移学习的概念引入了自然语言处理NLP)领域。

迁移学习是指将一个在大型通用数据集上训练的模型,通过在特定用例的小型数据集上微调,使其适用于特定的用例。

ULMFiT 的迁移学习方法可以总结如下:

  1. 从一个在大型文本数据集上训练的大型语言模型开始。

  2. 在与特定用例相关的文本数据集上微调该语言模型。

  3. 使用微调后的语言模型为特定用例创建文本分类器。

在本章中,大型模型被称为AWD_LSTM,它是在一个来自维基百科文章的大型语料库上训练的。我们将根据两个特定用例的数据集对该大型语言模型进行微调:IMDb 电影评论数据集和 Covid 推文数据集,用于社交媒体关于 Covid-19 疫情的帖子。然后,我们将使用每个微调后的语言模型来训练每个用例的文本分类器。

以下是本章中将要介绍的实例:

  • 使用精心挑选的 IMDb 文本数据集训练深度学习语言模型

  • 使用精心挑选的文本数据集训练深度学习分类模型

  • 使用独立文本数据集训练深度学习语言模型

  • 使用独立文本数据集训练深度学习文本分类器

  • 测试你的知识

技术要求

确保你已经完成了第一章的设置部分,快速入门 fastai,并且有一个有效的ch4文件夹。该文件夹包含本章中描述的代码示例。

本章中的一些示例可能需要一个多小时才能运行。

注意

请不要使用 Colab 来运行这些示例。在 Colab 中,你无法控制会话使用的 GPU,而这些示例可能需要很长时间才能运行。对于本章中的示例,请使用付费的 GPU 支持的 Gradient 环境,以确保它们能在合理时间内完成。

使用精心挑选的 IMDb 文本数据集训练深度学习语言模型

在本节中,你将了解如何使用 fastai 在精心挑选的文本数据集上训练语言模型。我们将使用一个与 fastai 一起打包的现有语言模型,并通过精心挑选的文本数据集 IMDb 对其进行微调,该数据集包含电影评论用例的文本样本。最终结果将是一个具有现有语言模型的广泛语言能力,同时具备 IMDb 数据集的特定用例细节。本食谱展示了 fastai 团队所取得的突破之一,即将迁移学习应用于自然语言处理(NLP)。

准备工作

在本书的前几章中,我们推荐使用 Gradient 环境。你可以在此食谱中使用 Gradient,下面的说明包括几个解决方法,帮助你在 Gradient 中运行该食谱。特别地,如果预训练的AWD_LSTM模型的初始设置被中断,且IMDB模型目录不可写,Gradient 中将无法使用该模型。如果预训练的AWD_LSTM模型设置被中断,请按照以下步骤操作:

  1. 在 Colab 中,运行text_model_training.ipynb笔记本中的单元格,直到并包括学习者定义和训练单元格。完成后,将/root/.fastai/models/wt103-fwd目录的内容复制到你的 Drive 环境中的一个文件夹里。

  2. 将你在上一步复制的文件上传到 Gradient 环境中的/storage/models/wt103-fwd目录。

完成这些步骤后,你现在应该能够在 Gradient 中运行本食谱的笔记本(以及其他使用AWD_LSTM的食谱)。

我感谢能有机会在本章中使用 IMDB 数据集。

数据集引用

Andrew L. Maas, Raymond E. Daly, Peter T. Pham, Dan Huang, Andrew Y. Ng, Christopher Potts. (2011) 学习情感分析的词向量 (ai.stanford.edu/~amaas/papers/wvSent_acl2011.pdf)

操作步骤…

在本节中,您将通过运行text_model_training.ipynb笔记本来使用 IMDb 精心策划的数据集训练语言模型。一旦您在 Colab 中打开笔记本,请完成以下步骤:

  1. 运行笔记本中直到Training a language model单元格的单元格。

  2. 运行以下单元格以定义与 IMDb 精心策划的数据集相关联的path对象:

    path = untar_data(URLs.IMDB)
    
  3. 您可以在imdb目录中运行tree -d命令的输出中看到此数据集的目录结构(在 Gradient 中为/storage/data/imdb)。请注意数据集的标签(评论是积极的还是消极的)是由文本样本所在的目录编码的。例如,训练数据集中负面评论文本样本包含在train/neg目录中。

    以下是在imdb目录中运行tree -d命令的输出:

    ├── test
    │   ├── neg
    │   └── pos
    ├── tmp_clas
    ├── tmp_lm
    ├── train
    │   ├── neg
    │   └── pos
    └── unsup
    
  4. 运行以下单元格以定义TextDataLoaders对象:

    dls =TextDataLoaders.from_folder(\
    path, valid = 'test', is_lm=True, bs=16)
    

    以下是TextDataLoaders对象定义的参数:

    a) path: 您在笔记本中先前定义的与 IMDb 精心策划的数据集相关联的path对象。

    b) valid: 标识将用于评估模型性能的数据集目录结构中的文件夹:imdb/test

    c) is_lm: 设置为True以指示此对象将用于语言模型(而不是文本分类器)。

    d) bs: 指定批量大小。

    注意

    当您训练大型数据集(如 IMDb)的语言模型时,将bs值调整为低于默认批量大小64将是避免内存错误的关键,因此在此TextDataLoaders定义中将其设置为16

  5. 运行以下单元格以显示样本批次中的几个项目:

    dls.show_batch(max_n=2)
    

    max_n参数指定要显示的样本批次项数。

    注意此单元格的输出。text列显示原始文本。text_列显示相同文本向前移动一个标记,即从原始文本的下一个词开始,直到原始文本的下一个词。例如,给定text列中的一个样本,语言模型将预测下一个单词,如text_列中所示。我们可以在以下截图中看到show_batch()的输出:

    图 4.1 – show_batch()的输出

    图 4.1 – show_batch()的输出

  6. 运行以下单元格以定义和训练深度学习模型:

    learn = language_model_learner(\
    dls,AWD_LSTM, metrics=accuracy).to_fp16()
    learn.fine_tune(1, 1e-2)
    

    以下是language_model_learner对象定义的参数:

    a) dls: 在此笔记本中先前定义的TextDataLoaders对象。

    b) AWD_LSTM: 用作此模型基础的预训练模型。这是在 Wikipedia 上使用 fastai 训练的预训练语言模型。如果您在 Colab 上运行此笔记本,在运行此单元格后,您可以在/root/.fastai/models/wt103-fwd目录中找到构成此模型的文件。

    c) metrics:模型需要优化的性能指标,在此案例中为准确度。

    以下是fine_tune语句的参数:

    a) 纪元数(第一个参数)指定了训练过程中算法遍历完整训练数据的次数。

    b) 学习率(第二个参数)指定了训练过程中的学习率。学习率是算法在学习最优参数时的步长。

    注意

    a) 根据你的环境,运行该单元格可能需要超过一小时才能完成。强烈建议你使用收费的 GPU 支持的 Gradient 环境,并为实例指定至少 3 小时,以确保其在合理的时间内完成并且实例在该单元格运行时不会关闭。

    b) language_model_learner定义中包括了调用to_fp16()来指定混合精度训练(简要说明:docs.fast.ai/callback.fp16.html#Learner.to_fp16),以减少训练过程中的内存消耗并防止内存错误。更多细节请参考更多内容…部分。

    fine_tune语句的输出显示了模型的准确率以及完成微调所需的时间,如下方截图所示:

    图 4.2 – 语句的输出

    图 4.2 – fine_tune语句的输出

  7. 运行以下单元格以执行你刚刚训练的语言模型:

    learn.predict("what comes next", n_words=20)
    

    这是该语言模型调用的参数:

    a) 输入文本样本 "what comes next"(第一个参数)是模型将完成的短语。语言模型将预测接下来应该跟随哪些词语。

    b) n_words:这是语言模型应预测的单词数,用来完成输入的短语。

    以下截图展示了模型预测结果可能的样子:

    图 4.3 – 语言模型完成一个短语

    图 4.3 – 语言模型完成一个短语

  8. 运行以下单元格以保存模型。你可以更新单元格来指定保存模型的目录和文件名:

    learn.export('/notebooks/temp/models/lm_model_'+modifier)
    
  9. 运行以下单元格以保存当前路径值:

    keep_path = learn.path
    
  10. 运行以下单元格以为学习器对象路径指定一个新值。这样做的原因是,默认情况下,模型的位置在 Gradient 上不可写,因此你需要更改路径值为你具有写入权限的目录:

    learn.path = Path('/notebooks/temp')
    
  11. 运行以下单元格以保存模型的编码器子集。这个模型去除了最后一层。在你训练文本分类器时,你将在使用精心策划的文本数据集训练深度学习分类模型部分使用这个模型:

    learn.save_encoder('ft_'+modifier)
    

恭喜!你已经成功地将迁移学习应用于在精心挑选的 IMDb 数据集上训练语言模型。需要注意的是,像这样将迁移学习应用于 NLP 的想法,直到 2018 年才首次被提出。现在,感谢 fastai 框架,只需几行代码,你就能利用一种在四年前几乎不存在的技术!

它是如何工作的…

在这一部分,你已经看到了如何使用经过精心挑选的文本数据集训练语言模型的简单示例。语言模型通过使用在庞大的维基数据集上预训练的AWD_LSTM模型,并使用 IMDb 数据集进行微调来创建。

通过这种方式利用迁移学习,我们最终得到了一个语言模型,它不仅具备了在通用英语方面的较强能力(得益于在维基数据集上预训练的模型),还能生成与电影评论用例相关的文本(得益于 IMDb 数据集)。

值得稍微深入了解一下本食谱中的模型。关于该模型的详细描述超出了本书的范围,因此我们将重点关注其中的一些要点。

如食谱所示,模型被定义为language_model_learner对象(文档链接:docs.fast.ai/text.learner.html#language_model_learner)。这个对象是 fastai learner对象的一个特化版本,你第一次看到这个对象是在理解世界的四种应用:表格、文本、推荐系统部分,以及第一章**,快速上手 fastai中的图片中。

本食谱中的模型基于预定义的AWD_LSTM模型(文档链接:docs.fast.ai/text.models.awdlstm.html#AWD_LSTM)。对于这个模型,learn.summary()的输出仅显示高级结构,包括 LSTM 层(传统 NLP 深度学习模型的基础)和 dropout 层(用于减少过拟合)。类似地,该模型的learn.model输出从编码层开始(即将输入数据转换为模型内部使用的中间表示),以解码层结束(即将内部表示转换回单词)。

还有更多…

在本章中,你将使用一些非常大的数据集,这意味着你可能需要采取额外的步骤,以确保在准备数据集和训练模型时不会耗尽内存。在这里,我们将描述一些步骤,帮助你确保在处理文本数据集时,不会因为内存不足而无法训练你的 fastai 深度学习模型。我们还会详细讨论如何在 Gradient 中保存编码器。

如果内存不足会发生什么?

如果你正在尝试训练一个大型模型,可能会遇到类似以下的内存不足提示:

RuntimeError: CUDA out of memory. Tried to allocate 102.00 MiB (GPU 0; 7.93 GiB total capacity; 7.14 GiB already allocated; 6.50 MiB free; 7.32 GiB reserved in total by PyTorch)

该消息表示你的环境中的 GPU 内存已满。如果遇到这种内存错误,你可以采取哪些措施呢?有三种方法可以帮助你解决此类内存错误:

  • 显式设置批量大小。

  • 使用混合精度训练。

  • 确保一次只激活一个笔记本。

内存错误缓解方法 #1:显式设置批量大小

要显式设置批量大小,你可以重新启动笔记本的内核,然后更新TextDataLoaders对象的定义来设置bs参数,如下所示:

dls = TextDataLoaders.from_folder(untar_data(URLs.IMDB), valid='test', bs=16)

设置bs参数可以显式指定批量大小(即用于计算平均损失的数据项数量),而不是默认值 64。通过显式将批量大小设置为比默认值小的值,你限制了每个训练周期(即完全遍历训练数据)消耗的内存量。当你像这样显式设置bs参数的值时,确保所设置的bs参数值是 8 的倍数。

内存错误缓解方法 #2:混合精度训练

另一个可以用来控制内存消耗的技巧是将to_fp16()函数应用于学习者对象的定义,如下所示:

learn = language_model_learner(dls,AWD_LSTM,
drop_mult=0.5,metrics=accuracy).to_fp16()

通过指定此to_fp16()调用,你可以让模型使用精度较低的浮点数进行训练,从而节省内存。结果是,模型训练过程消耗的内存更少。有关更多详细信息,请参考 fastai 文档:docs.fast.ai/callback.fp16.html#Learner.to_fp16

内存错误缓解方法 #3:坚持使用单个活跃的笔记本

最后,另一种防止内存不足的方法是一次运行一个笔记本。例如,在 Gradient 中,如果你同时激活多个笔记本,可能会耗尽可用内存。如果你已经在TextDataLoaders对象中设置了较小的批量大小,并在学习者对象中指定了to_fp16(),但仍然出现内存错误,那么可以关闭除当前正在使用的笔记本之外的所有笔记本的内核。

在 Gradient 的 JupyterLab 中,你可以通过右键点击导航面板中的笔记本,然后从菜单中选择关闭内核来关闭一个笔记本的内核,如下图所示:

图 4.4 – 在 Gradient JupyterLab 中关闭内核

图 4.4 – 在 Gradient JupyterLab 中关闭内核

解决方法允许你保存编码器

除了我们刚才回顾的内存优化技巧之外,如果你在使用 Gradient 时,还需要知道一个针对文本模型的额外技巧。在 Gradient 中,你可能会遇到无法在 fastai 希望保存的目录中保存和检索临时对象的情况。

例如,你需要保存语言模型的编码器,并在训练文本分类器时加载该编码器。然而,fastai 强制你将编码器保存在数据集的路径中。save_encoder 函数只允许你指定不带路径的文件名,而不是指定保存编码器的目录,正如你在下面的 save_encoder 调用中看到的那样:

learn.save_encoder('ft_'+modifier)

与此同时,在 Gradient 中,IMDb 数据集的目录 /storage/data/imdb 是只读的。那么,如果必须保存的目录不可写,如何保存编码器呢?你可以通过临时更新学习者的 path 对象,先将编码器保存在该临时 path 值指示的目录中,然后将 path 对象设置回原始值来解决这个问题,如下所示:

  1. 保存模型的路径值:

    keep_path = learner.path
    
  2. 将模型的路径值更改为你有写权限的目录,例如:

    learner.path = Path('/temp/models')
    
  3. 保存模型。

  4. 将路径恢复为原始值:

    learner.path = keep_path
    

使用精心整理的文本数据集训练深度学习分类模型

在前一部分中,我们使用整理过的 IMDb 文本数据集训练了一个语言模型。前一部分中的模型预测给定一组单词后,接下来的一组单词。在本节中,我们将使用在 IMDb 数据集上微调的语言模型,来训练一个文本分类模型,该模型用于分类特定于电影评论的文本样本。

准备工作

本配方使用了你在前一部分中训练的编码器,因此请确保你已经按照该部分中的配方步骤进行操作,特别是已经保存了从训练语言模型中得到的编码器。

正如前一部分所述,在 Gradient 中运行使用了在 Wikipedia 语料库上预训练的语言模型的配方之前,你需要执行一些额外的步骤。为了确保你可以使用在本配方中所需的预训练语言模型,如果 AWD_LSTM 的设置被中断,请完成以下步骤。

  1. 在 Colab 中,运行 text_model_training.ipynb 笔记本中的单元格,直到并包括学习者定义和训练单元格。完成后,将 /root/.fastai/models/wt103-fwd 目录中的内容复制到你的 Drive 环境中的一个文件夹。

  2. 将你在上一步复制的文件上传到 Gradient 环境中的 /storage/models/wt103-fwd 目录。

按照这些步骤,你就可以在 Gradient 中运行此配方的笔记本(以及其他使用 AWD_LSTM 的配方)。

如何操作…

在本节中,你将通过运行text_classifier_model.ipynb笔记本,使用IMDb整理的数据集训练一个文本分类深度学习模型。打开笔记本后,完成以下步骤:

  1. 运行笔记本中的单元,直到定义文本分类器单元。

  2. 运行以下单元来定义一个TextDataLoaders对象:

    dls_clas = TextDataLoaders.from_folder(\
    path, valid='test')
    

    下面是TextDataLoaders对象定义的参数:

    a) path:定义用于定义TextDataLoaders对象的数据集路径

    b) valid:标识数据集目录结构中用于评估模型性能的文件夹:imdb/test

  3. 运行以下单元查看一个批次中的样本条目:

    dls_clas.show_batch(max_n=3)
    
  4. show_batch()的输出展示了文本样本及其类别(在category列中表示)。fastai 知道类别是编码在文本样本所在的目录中的,并在show_batch()中正确显示,如下图所示:图 4.5 – 输出

    图 4.5 – show_batch()输出

  5. 运行以下单元来定义文本分类模型:

    learn_clas = text_classifier_learner(dls_clas, AWD_LSTM, 
                                    metrics=accuracy).to_fp16()
    

    下面是text_classifier_learner对象定义的参数:

    a) dls_clas:这是在前一个单元中定义的TextDataLoaders对象。

    b) AWD_LSTM:这是作为该模型基础使用的预训练模型。如果你在 Colab 中运行此笔记本,你可以在运行此单元后,找到组成该模型的文件,路径是/root/.fastai/models/wt103-fwd

    c) metrics:这是要优化的模型性能指标,在此案例中为准确率。

  6. 你需要加载在上一节中作为配方保存的编码器。第一步是为learn_clas对象设置路径,使其指向保存编码器的路径,运行以下单元即可。确保指定的目录是你在上一节保存编码器的目录:

    learn_clas.path = Path('/notebooks/temp')
    
  7. 运行以下单元来加载你在使用整理文本数据集训练深度学习语言模型一节中保存的编码器到learn_clas对象:

    learn_clas = learn_clas.load_encoder('ft_'+modifier)
    
  8. 运行以下单元来训练模型:

    learn_clas.fit_one_cycle(5, 2e-2)
    

    下面是fit_one_cycle的参数:

    a) 参数 epoch 计数(第一个参数)指定训练运行5个 epoch。

    b) 参数学习率(第二个参数)指定学习率为0.02

    该单元的输出显示了训练结果,包括每个 epoch 的准确度和所需时间,如下图所示:

    图 4.6 – 文本分类模型训练结果

    图 4.6 – 文本分类模型训练结果

  9. 运行这些单元,对你期望为负面和正面的文本字符串进行预测,并观察训练好的模型是否做出了预期的预测,如以下截图所示:

图 4.7 – 使用文本分类器对文本字符串进行预测

图 4.7 – 使用文本分类器对文本字符串进行预测

恭喜你!你已经将经过 IMDb 数据集微调的语言模型用于训练文本分类模型,该模型对特定于电影评论的文本样本进行分类。

它是如何工作的…

你可以通过对比本节定义文本分类器的代码与前一节定义语言模型的代码,看到 fastai 的强大。总共只有三点差异:

  1. TextDataLoaders对象的定义中,以下内容适用:

    a) 语言模型具有is_lm参数,用于指示该模型是一个语言模型。

    b) 文本分类器具有label_col参数,用于指示数据集中的哪一列包含模型正在预测的类别。在本节定义的文本分类器中,数据集的标签是通过数据集的目录结构进行编码的,而不是数据集中的一列,因此在定义TextDataLoaders对象时不需要该参数。

  2. 在模型定义中,以下内容适用:

    a) 语言模型定义了一个language_model_learner对象。

    b) 文本分类器定义了一个text_classifier_learner对象。

  3. 在从模型获取预测时,以下内容适用:

    a) 语言模型在调用learn.predict()时有两个参数,第一个是用来进行预测的字符串,第二个是要预测的单词数量。

    b) 文本分类器在调用learn.predict()时有一个参数,即模型将预测其类别的字符串。

仅通过这三点差异,fastai 就处理了语言模型和文本分类器之间的所有底层差异。

还有更多内容…

如果你使用的是 Gradient 环境,并且正在使用带有费用的笔记本,你可能需要控制笔记本的活跃时间,以避免支付超过所需的时间。你可以在启动时通过从自动关机菜单中选择小时数来选择会话持续时间,如下截图所示:

图 4.8 – 为 Gradient 笔记本会话选择持续时间

图 4.8 – 为 Gradient 笔记本会话选择持续时间

假设你选择了比实际需要更长的时间,并且在自动关机时间限制到达之前就完成了会话。那么,你是否应该显式地关闭会话?

我的经验是,如果你尝试在 Gradient 笔记本界面中通过选择停止实例按钮(如图 4.9所示)来停止实例,你将面临将实例置于无法轻松重新启动的状态的风险:

图 4.9 – 在 Gradient 中的实例视图中停止实例按钮

图 4.9 – 在 Gradient 中的实例视图中的“停止实例”按钮

如果您选择停止实例并且您的实例进入无法重新启动的状态,那么您必须联系Paperspace支持团队来修复您的实例。在这种情况发生了几次之后,我停止使用停止实例按钮,而是等实例在我完成工作后超时自动停止。通过不显式停止您的 Gradient 实例,而是让其在会话结束时超时,您将节省更多时间。

使用独立文本数据集训练深度学习语言模型

在前面的章节中,我们使用精选的文本数据集 IMDb 训练了一个语言模型和文本分类器。在这一节和接下来的章节中,我们将使用一个独立的文本数据集——Kaggle 新冠推文 NLP – 文本分类数据集,进行语言模型和文本分类器的训练。该数据集可以在这里找到:www.kaggle.com/datatattle/covid-19-nlp-text-classification。该数据集包含与新冠疫情相关的一些推文,并且对这些推文进行了五个类别的分类:

  • 极度负面

  • 负面

  • 中立

  • 正面

  • 极度正面

在这个数据集上训练的语言模型的目标是根据一个起始短语预测新冠相关推文中的后续单词。在这个数据集上训练的文本分类模型的目标,如使用独立文本数据集训练深度学习文本分类器一节中所述,是预测一个短语属于五个类别中的哪一个。

准备工作

如本章前面所述,在您能够在 Gradient 中运行此食谱之前,您需要采取一些额外的步骤,以确保您可以访问将在此食谱中使用的预训练语言模型。如果您尚未执行这些步骤,请按照以下步骤准备 Gradient 环境(如果 AWD_LSTM 的设置中断了):

  1. 在 Colab 中,运行text_model_training.ipynb笔记本的单元格,直到包括学习者定义和训练单元格为止。完成后,将/root/.fastai/models/wt103-fwd目录中的内容复制到您的 Drive 环境中的文件夹中。

  2. 将您在上一步中复制的文件上传到 Gradient 环境中的/storage/models/wt103-fwd目录。

完成这些步骤后,您现在应该能够在 Gradient 中运行此食谱(以及其他使用AWD_LSTM的食谱)。

确保您已将构成独立新冠相关推文数据集的文件上传到您的 Gradient 环境中,可以按照以下步骤操作:

  1. www.kaggle.com/datatattle/covid-19-nlp-text-classification下载archive.zip文件。

  2. 解压下载的archive.zip文件,提取Corona_NLP_test.csvCorona_NLP_train.csv文件。

  3. 在 Gradient 环境的终端中,将/storage/archive设置为当前目录:

    cd /storage/archive
    
  4. 创建/storage/archive/covid_tweets目录:

    mkdir covid_tweets
    
  5. /storage/archive/covid_tweets设置为当前目录:

    cd /storage/archive/covid_tweets
    
  6. /storage/archive/covid_tweets中创建testtrain目录:

    mkdir test
    mkdir train
    
  7. 将您在步骤 2中解压的文件(Corona_NLP_test.csvCorona_NLP_train.csv)上传到/storage/archive/covid_tweets。您可以使用 Gradient 中的 JupyterLab 的上传按钮进行上传,但需要分几步完成:

    a) 在 Gradient 环境的终端中,将/notebooks设置为当前目录:

    notebooks/temp directory, make a new /notebooks/temp directory:
    
    

    在临时文件夹中,选择上传按钮(见图 4.10),并从本地系统文件夹中选择在步骤 2中解压的Corona_NLP_test.csvCorona_NLP_train.csv文件:

    
    

图 4.10 – JupyterLab 中的上传按钮

图 4.10 – JupyterLab 中的上传按钮

d) 在 Gradient 环境的终端中,将Corona_NLP_test.csv文件复制到/storage/archive/covid_tweets/test目录:

cp /notebooks/temp/Corona_NLP_test.csv /storage/archive/covid_tweets/test/Corona_NLP_test.csv

e) 将Corona_NLP_train.csv文件复制到/storage/archive/covid_tweets/train目录:

cp /notebooks/temp/Corona_NLP_train.csv /storage/archive/covid_tweets/train/Corona_NLP_train.csv

一旦您完成上传构成 Covid 相关推文数据集的文件步骤,您应该在 Gradient 环境中的/storage/archive/covid_tweets目录下拥有以下目录结构:

├── test
│   └── Corona_NLP_test.csv
└── train
    └── Corona_NLP_train.csv

通过这些准备步骤,您已将构成数据集的文件放入 Gradient 环境中的正确位置,以供 fastai 模型使用。

我感谢有机会在本书中包含 Covid 推文数据集,并感谢该数据集的策展人以及 Kaggle 使该数据集得以公开。

数据集引用

Aman Miglani (2020). Coronavirus tweets NLP - Text Classification (www.kaggle.com/datatattle/covid-19-nlp-text-classification)

如何操作…

在本节中,您将运行text_standalone_dataset_lm.ipynb笔记本,使用与 Covid 相关的推文独立数据集训练语言模型。打开笔记本后,完成以下步骤:

  1. 运行笔记本中的单元格直到Ingest the dataset单元格。

  2. 运行以下单元格来定义数据集的路径对象:

    path = URLs.path('covid_tweets')
    

    注意

    该路径定义的参数是您在 Gradient 环境中复制了数据集 CSV 文件的目录层次结构根目录的名称。

  3. 运行以下单元格来定义一个df_train数据框,用于包含Corona_NLP_train.csv文件的内容:

    df_train = pd.read_csv(path/'train/Corona_NLP_train.csv',
                           encoding = "ISO-8859-1")
    

    以下是定义数据框架的参数:

    a) path/'train/Corona_NLP_train.csv'参数指定了数据集训练部分的部分文件名。

    b) encoding = "ISO-8859-1"参数指定用于文件的编码。此编码选择是为了确保 CSV 文件的内容可以无误地导入到数据框中。

  4. 运行以下单元格以定义TextDataLoaders对象:

    dls = TextDataLoaders.from_df(df_train, path=path, 
                                 text_col='OriginalTweet',
                                 is_lm=True)
    

    以下是TextDataLoaders对象定义的参数:

    a) df_train:你在上一步中创建的数据框。

    b) path:数据集的路径对象。

    c) text_col:数据框中包含用于训练模型的文本的列。在这个数据集里,OriginalTweet列包含了用于训练模型的文本。

    d) is_lm:指示该模型是语言模型的标志。

  5. 运行以下单元格以定义并训练带有language_model_learner对象的深度学习模型:

    learn = language_model_learner(dls,AWD_LSTM,
                               metrics=accuracy).to_fp16()
    learn.fine_tune(1, 1e-2)
    

    注意

    language_model_learner对象的定义包括调用to_fp16()来指定混合精度训练(总结见:docs.fast.ai/callback.fp16.html#Learner.to_fp16),以减少训练过程中的内存消耗。

    以下是language_model_learner对象定义的参数:

    a) dls:你在上一步中定义的TextDataLoaders对象。

    b) AWD_LSTM:用作此模型基础的预训练模型。这是与 fastai 结合使用的预训练语言模型,使用维基百科进行训练。

    c) metrics:模型优化的性能指标,在本例中为准确率。

    以下是fine_tune语句的参数:

    a) epoch 计数参数(第一个参数)指定训练过程中的迭代次数。

    b) 学习率参数(第二个参数)指定训练过程中的学习率。

    训练过程的结果,如图 4.11所示,在执行fine_tune语句后显示:

    图 4.11 – 训练过程的结果

    图 4.11 – 训练过程的结果

  6. 运行以下单元格以运行训练好的语言模型:

    learn.predict("what comes next", n_words=20)
    

    结果如下所示:

    图 4.12 – 在独立文本数据集上训练的语言模型的预测

    图 4.12 – 在独立文本数据集上训练的语言模型的预测

  7. 运行以下单元格以保存模型。你可以更新单元格来指定保存模型的目录和文件名:

    learn.export('/notebooks/temp/models/lm_model_standalone'+modifier)
    
  8. 运行以下单元格以保存当前路径值:

    keep_path = learn.path
    
  9. 运行以下单元格以为学习器对象路径分配新值。这样做的原因是,模型的默认位置在 Gradient 上不可写,因此你需要将路径值更改为你有写入权限的目录:

    learn.path = Path('/notebooks/temp')
    
  10. 运行以下单元格以保存语言模型的编码器子集。这是去除最后一层的模型。你将在下一部分中使用这个编码器,当你在 Covid 相关推文的独立数据集上训练文本分类器时将用到它:

    learn.save_encoder('ft_standalone'+modifier)
    

恭喜!你使用 fastai 在现有模型的基础上进行了迁移学习,并结合 Covid 相关推文独立数据集创建了一个经过微调的语言模型。在下一部分中,你将使用上一步保存的编码器来微调一个基于独立数据集训练的文本分类器。

它是如何工作的…

在这一部分,你已经看到了一个简单的例子,展示了如何使用 fastai 和独立文本数据集训练语言模型。语言模型的创建是通过使用一个已经在海量 Wikipedia 数据集上训练的现有模型(AWD_LSTM),然后使用独立的 Covid 相关推文数据集对其进行微调。

通过这种方式利用迁移学习,我们得到了一个语言模型,既具备了良好的通用英语能力(得益于在 Wikipedia 数据集上预训练的模型),也具备了生成与 Covid-19 大流行相关的社交媒体内容的能力(得益于 Covid 推文数据集)。通过遵循本节中的教程,你可以利用 fastai 在其他文本数据集和用例中应用这种方法(NLP 的迁移学习)。

使用独立文本数据集训练深度学习文本分类器

使用独立文本数据集训练深度学习语言模型部分,我们使用独立文本数据集训练了一个语言模型:Kaggle 上的“Coronavirus tweets NLP – Text Classification”数据集,详细描述请见这里:www.kaggle.com/datatattle/covid-19-nlp-text-classification。在这一部分,我们将使用这个语言模型创建一个基于与 Covid 相关的推文数据集训练的文本分类器。

准备工作

这个教程使用了你在使用独立文本数据集训练深度学习语言模型部分中训练的编码器,因此请确保你已按照该部分中的教程步骤进行操作,特别是要确保你已经保存了上部分训练的语言模型中的编码器。

同时,请确保你已按照上一部分的准备工作子节中的所有步骤,确保以下几点:

  • 你已在 Gradient 环境中访问到AWD_LSTM模型。

  • 你已经将包含独立 Covid 相关推文数据集的文件(Corona_NLP_test.csvCorona_NLP_train.csv)上传到你的 Gradient 环境中。

如何操作...

在本节中,你将通过 text_standalone_dataset_classifier.ipynb 笔记本来训练一个文本分类深度学习模型,使用的是与 Covid 相关的推文数据集。打开笔记本后,按以下步骤操作:

  1. 运行笔记本中的单元格,直到 Ingest the dataset 单元格。

  2. 运行以下单元格来定义数据集的路径对象。注意,参数是你在 Gradient 环境中复制 CSV 文件所在的根目录的名称:

    path = URLs.path('covid_tweets')
    
  3. 运行以下单元格来定义一个数据框,包含 Corona_NLP_train.csv 文件的内容(与 Covid 相关的推文数据集的训练部分):

    df_train = pd.read_csv(path/'train/Corona_NLP_train.csv',
                           encoding = "ISO-8859-1")
    

    以下是定义数据框的参数:

    a) path/'train/Corona_NLP_train.csv' 参数指定了数据集训练部分的部分限定文件名。

    b) encoding = "ISO-8859-1" 参数指定了用于文件的编码格式。选择此编码格式是为了确保 CSV 文件的内容能够无误地导入到数据框中。

  4. 运行以下单元格来定义 TextDataLoaders 对象:

    dls = TextDataLoaders.from_df(df_train, path=path, text_col='OriginalTweet',label_col='Sentiment')
    

    以下是定义 TextDataLoaders 对象的参数:

    a) df_train:你在前一步中创建的数据框。

    b) path:数据集的路径对象。

    c) text_col:数据框中包含将用于训练模型的文本的列。对于该数据集,OriginalTweet 列包含用于训练模型的文本。

    d) label_col:数据框中包含文本分类器将预测的标签的列。

  5. 运行以下单元格来查看你在前一步中定义的 TextDataLoaders 对象中的一个批次:

    dls.show_batch(max_n=3)
    

    该语句的输出,textcategory 列,将如下所示:

    图 4.13 – 来自 Covid 推文数据集的批次

    图 4.13 – 来自 Covid 推文数据集的批次

  6. 运行以下单元格来定义 text_classifier_learner 对象,以用于文本分类模型:

    learn_clas = text_classifier_learner(dls, AWD_LSTM, 
                               metrics=accuracy).to_fp16()
    

    注意

    text_classifier_learner 对象的定义包括调用 to_fp16() 来指定混合精度训练(此处总结:docs.fast.ai/callback.fp16.html#Learner.to_fp16),以减少训练过程中内存的消耗。

    以下是定义 text_classifier_learner 对象的参数:

    a) dls:你在前一步中定义的 TextDataLoaders 对象。

    b) AWD_LSTM:用作此模型基础的预训练模型。它是与 fastai 集成的预训练语言模型,已使用维基百科进行训练。

    c) metrics:要优化的模型性能指标,在此案例中为准确度。

  7. 运行以下单元格,为学习器对象路径分配一个新值。这样做的原因是将路径设置为与你在前一部分保存编码器的目录匹配:

    learn_clas.path = Path('/notebooks/temp')
    
  8. 运行以下代码单元,将你在训练深度学习语言模型与独立文本数据集部分保存的编码器加载到learn_clas对象中:

    learn_clas =\
    learn_clas.load_encoder('ft_standalone'+modifier)
    
  9. 运行以下代码单元,重置学习器对象路径的值:

    learn_clas.path = keep_path
    
  10. 运行以下单元格来训练模型:

    learn_clas.fit_one_cycle(1, 2e-2)
    

    这是fit_one_cycle的参数:

    a) 纪元计数参数(第一个参数)指定训练运行 1 个纪元。

    b) 学习率参数(第二个参数)指定学习率为 0.02。

    该单元格的输出(如图 4.14所示)展示了训练的结果:

    图 4.14 – 文本分类器训练的输出

    图 4.14 – 文本分类器训练的输出

  11. 运行单元格,对你预期为负面和正面的文本字符串进行预测,观察训练好的模型是否做出了预期的预测(如图 4.15所示):

图 4.15 – 使用文本分类器对文本字符串进行预测

图 4.15 – 使用文本分类器对文本字符串进行预测

恭喜!你已经利用 fastai 的功能,在独立数据集上使用迁移学习训练了文本分类器。

它是如何工作的…

针对独立的 Covid 相关推文数据集,文本分类器模型的代码与针对精心策划的 IMDb 文本数据集的文本分类器模型代码有一些不同。让我们来看一下其中的一些差异。

对于 IMDb 数据集,TextDataLoaders定义不包括label_col参数:

dls_clas = TextDataLoaders.from_folder(path, valid='test')

相比之下,独立数据集的TextDataLoaders定义包括text_collabel_col参数:

dls = TextDataLoaders.from_df(df_train, path=path, text_col='OriginalTweet',label_col='Sentiment')

为什么会有这些差异?首先,对于IMDb数据集,我们使用TextDataLoadersfrom_folder变体,因为数据集是按单独的文本文件组织的,而每个文件所属的类别由该文件所在的目录编码。以下是 IMDb 数据集的目录结构:

├── test
│   ├── neg
│   └── pos
├── tmp_clas
├── tmp_lm
├── train
│   ├── neg
│   └── pos
└── unsup

考虑 IMDb 数据集中的一个文件,train/pos/9971_10.txt

这部电影非常棒,我觉得原版还算平庸。不过这部作品却具备了所有要素,一辆 1970 年的 Hemi Challenger,配备四速变速器,真的展示了母亲 Mopar 如何打造出最棒的肌肉车!每次 Kowalski 踩下那大块 Hemi 时,我仿佛置身于克莱斯勒的天堂,而他确实经常这么做 😃

当我们训练文本分类模型时,fastai 如何知道这篇评论的类别?它知道,因为这个文件位于/pos目录中。得益于 fastai 的灵活性,我们只需将path值传递给TextDataLoaders对象的定义,fastai 框架会自动识别数据集中每个文本样本的类别。

现在,让我们来看一下独立的与 Covid 相关的推文数据集。这个数据集被打包为 CSV 文件,格式如下:

图 4.16 —— 来自与 Covid 相关的推文数据集的样本

图 4.16 —— 来自与 Covid 相关的推文数据集的样本

与 IMDb 数据集不同,Covid 相关的推文数据集只包含两个文件(一个用于训练数据集,一个用于测试数据集)。这些文件的列包含 fastai 训练模型所需的信息:

  • 样本的文本 —— 在OriginalTweet列中

  • 样本的类别(也称为标签)——在Sentiment列中

为了告诉 fastai 如何解读这个数据集,我们需要在TextDataLoaders对象的定义中明确告诉它文本所在的列和类别(或标签)所在的列,方法如下:

dls = TextDataLoaders.from_df(df_train, path=path, text_col='OriginalTweet',label_col='Sentiment')

IMDb数据集由成千上万的单独文本文件组成,这些文件分布在一个复杂的目录结构中,目录编码了每个文本文件的类别。相比之下,Covid 相关的推文数据集由两个 CSV 文件组成,其中列包含文本样本及其类别。尽管这两个数据集的组织方式不同,fastai 仍然能够加载它们并通过对TextDataLoaders对象定义进行少许调整,准备好用于训练深度学习模型。fastai 能够轻松加载各种格式的数据集,不仅对文本数据集有用,对所有种类的数据集都很有帮助。正如你将在第六章《使用视觉数据训练模型》中看到的那样,处理图像数据集时,我们从这一能力中受益匪浅,因为图像数据集有许多不同的组织方式。

测试你的知识

现在,你已经完成了多个使用文本数据集训练 fastai 深度学习模型的扩展示例,你可以尝试一些变化来练习你所学到的内容。

准备工作

确保你已按照使用独立文本数据集训练深度学习文本分类器部分中的准备工作步骤准备好你的 Gradient 环境并上传与 Covid 相关的推文数据集。

如何操作…

你可以按照本节中的步骤,尝试一些使用 Covid 相关推文数据集训练的模型的变化:

  1. 复制你在使用独立文本数据集训练深度学习语言模型部分中操作过的text_standalone_dataset_lm.ipynb笔记本。给你的新副本命名为:text_standalone_dataset_lm_combo.ipynb

  2. 在你的新笔记本中,除了为训练用的 CSV 文件 Corona_NLP_train.csv 创建一个数据框(dataframe),还需要通过在笔记本中添加一个类似于下面的单元格来为测试用的 CSV 文件 Corona_NLP_test.csv 创建一个数据框:

    df_test = pd.read_csv(path/'test/Corona_NLP_test.csv ',encoding = "ISO-8859-1")
    
  3. 使用 pandas 的 concat 函数将两个数据框合并成一个新的数据框,命名为 df_combo

    df_combo = pd.concat([df_train, df_test], axis=0)
    
  4. 现在更新你新笔记本的其余部分,使用 df_combo 代替 df_train,并运行整个笔记本来训练一个新的语言模型。你注意到模型性能有什么不同吗?

  5. 在大多数模型训练场景中,你需要确保不要使用测试数据集来训练模型。你能想出为什么可以用测试集来训练这样的语言模型吗?

恭喜!你已经完成了使用 fastai 在文本数据集上训练 fastai 深度学习模型的复习。

第五章:第五章:训练推荐系统

在本书中,到目前为止我们已经通过使用 fastai 训练深度学习的配方,处理了各种数据集。在本章中,我们将介绍一些配方,这些配方利用 fastai 对推荐系统(也称为协同过滤系统)的支持。推荐系统结合了在第三章《使用表格数据训练模型》中介绍的表格数据模型的特点,以及在第四章《使用文本数据训练模型》中介绍的文本数据模型的特点。

推荐系统涵盖了一个狭窄但已被验证的应用场景:给定一组用户及其对一组项目的评分,推荐系统可以预测用户对一个尚未评分的项目的评分。例如,给定一组书籍和一组读者对这些书籍的评价,推荐系统可以预测某位读者对一本他们尚未阅读的书籍的评价。

在本章中,你将学习如何通过处理一系列配方,利用 fastai 内建的推荐系统支持,在各种推荐系统数据集上训练模型。你将看到一些你在前几章中已经熟悉的 fastai 特性,以及一些独特的、仅适用于推荐系统的新特性。在完成本章内容后,你将能够使用 fastai 的高级 API 在你自己的数据集上创建推荐系统。

本章将涵盖以下配方:

  • 在小型精心策划的数据集上训练推荐系统

  • 在大型精心策划的数据集上训练推荐系统

  • 在独立数据集上训练推荐系统

  • 测试你的知识

技术要求

确保你已经完成了第一章《快速入门 fastai》中的设置部分,并且已经有了一个可用的 ch5 文件夹。这个文件夹包含了本章描述的代码示例。

在小型精心策划的数据集上训练推荐系统

你可能还记得,在第一章《快速入门 fastai》中,描述了 fastai 支持的应用,涵盖了四种类型的数据集:表格数据文本数据推荐系统图像数据。在第二章《使用 fastai 探索和清理数据》中,你看到了关于检查表格数据集、文本数据集和图像数据集的部分内容。

你可能会想,为什么没有关于检查推荐系统数据集的章节。原因是,在 fastai 中,推荐系统的数据导入过程与表格数据集的数据导入过程是相同的,正如你将在本节中看到的那样。虽然推荐系统的数据导入过程与表格数据集的导入过程相同,但 fastai 提供了专门针对推荐系统的模型训练细节。

在本节中,我们将介绍如何在一个精心挑选的数据集上训练推荐系统,以了解如何使用 fastai 训练推荐系统。

在本节中,你将使用一个小型的精心挑选的数据集——ML_SAMPLE来训练推荐系统。该数据集是 MovieLens 数据集的一个子集(grouplens.org/datasets/movielens),包含了用户对电影的评分。在在大规模精心挑选的数据集上训练推荐系统这一节中,我们将使用 MovieLens 数据集的更大子集——ML_100k来训练推荐系统。

准备就绪

确保你可以在仓库的ch5目录下打开training_recommender_systems.ipynb笔记本。

本节中使用的数据集以及在大规模精心挑选的数据集上训练推荐系统部分的数据集来自 MovieLens 数据集。我衷心感谢能够使用这个数据集来展示 fastai 的推荐系统功能。

数据集引用

Andrew L. Maas, Raymond E. Daly, Peter T. Pham, Dan Huang, Andrew Y. Ng, 和 Christopher Potts. (2011). 用于情感分析的词向量学习 (ai.stanford.edu/~amaas/papers/wvSent_acl2011.pdf)。第 49 届计算语言学协会年会(ACL 2011)www.aclweb.org/anthology/P11-1015

如何操作…

在本节中,你将运行training_recommender_systems.ipynb笔记本。一旦你在 fastai 环境中打开了该笔记本,完成以下步骤:

  1. 运行笔记本中的单元格,直到导入数据集单元格,以导入所需的库并设置笔记本。

  2. 运行以下单元格定义该数据集的path对象:

    path = untar_data(URLs.ML_SAMPLE)
    
  3. 运行以下单元格检查数据集的目录结构:

    path.ls()
    

    以下输出显示了数据集的目录结构:

    图 5.1 – path.ls()的输出

    图 5.1 – path.ls()的输出

  4. 运行以下单元格将数据集加载到df数据框中:

    df = pd.read_csv(path/'ratings.csv')
    
  5. 运行以下单元格查看数据集中的一些记录:

    df.head()
    

    输出,如图 5.2所示,列出了数据集中的记录。每条记录代表用户对电影的评分。userId 列包含用户的 ID,movieId 列包含电影的 ID,rating 列中的值是用户对电影的评分。例如,用户 73 给电影 1097 评分为 4.0:

    图 5.2 –  的输出

    图 5.2 – df.head() 的输出

  6. 运行以下代码单元来为数据集定义一个 CollabDataLoaders 对象:

    dls=CollabDataLoaders.from_df(df,bs= 64)
    

    CollabDataLoaders 对象的定义使用了以下参数:

    a) df:你在此笔记本中早些时候定义的 DataFrame

    b) bs:模型训练过程中的批次大小

  7. 运行以下代码单元,查看你在前一步定义的 CollabDataLoaders 对象的一个批次:

    dls.show_batch()
    

    输出显示了批次的内容,如下图所示:

    图 5.3 –  的输出

    图 5.3 – show_batch() 的输出

  8. 运行以下代码单元来定义推荐系统模型的 collab_learner 对象:

    learn=collab_learner(dls, y_range= [ 0 , 5.0 ] )
    

    以下是定义 collab_learner 对象时的参数:

    a) dls:你在前一步定义的 CollabDataLoaders 对象

    b) y_rangerating 列中的值范围

  9. 运行以下代码单元来训练协同过滤模型:

    learn.fit_one_cycle( 5 )
    

    以下是模型定义的参数:

    a) 5:模型训练运行的纪元数

    输出显示了训练和验证损失,如下图所示。你可以通过验证损失随纪元数的减少而降低,来观察训练的改进:

    图 5.4 – 训练协同过滤模型的输出

    图 5.4 – 训练协同过滤模型的输出

  10. 现在我们已经训练了推荐系统,我们想通过让它预测一些用户对某些电影的评分来进行测试。为了测试推荐系统,我们首先需要创建一些测试数据。创建测试数据,请运行以下代码单元,定义一个包含几个测试条目的 DataFrame:

    scoring_columns = ['userId','movieId']
    test_df = pd.DataFrame(columns=scoring_columns)
    test_df.at[0,'userId'] = 388
    test_df.at[0,'movieId'] = 153
    test_df.at[1,'userId'] = 607
    test_df.at[1,'movieId'] = 1210
    test_df.head()
    

    以下是该代码单元的关键元素:

    a) scoring_columns:一个包含 DataFrame 列名的列表。请注意,这些列名与 show_batch() 输出中看到的列名相同。

    b) test_df:包含测试条目的 DataFrame,列名由 scoring_columns 列表指定。

    test_df.head() 的输出显示了完成的 DataFrame 内容,如下图所示:

    图 5.5 –  DataFrame 的内容

    图 5.5 – test_df DataFrame 的内容

    这些 test_df DataFrame 中的条目是什么意思?我们将使用该 DataFrame 来查看推荐系统对于以下组合的预测:

    a) userId388 的用户对 movieId153 的电影评分。

    b) userId 607movieId 1210 的评分。

  11. 现在你已经定义了 test_df 数据框,包含了你想要测试推荐系统的条目,你可以使用它来测试已训练的推荐系统。运行以下代码单元来获取推荐系统对 test_df 中条目的评分预测:

    dl = learn.dls.test_dl(test_df)
    learn.get_preds(dl=dl)
    

    此代码单元的输出展示了推荐系统的结果,如下图所示:

图 5.6 – 推荐系统的结果

图 5.6 – 推荐系统的结果

这些结果意味着什么?推荐系统的预测结果如下:

a) userId 388movieId 153 的评分是 2.4156

b) userId 607movieId 1210 的评分是 3.6090

恭喜!你已经使用 fastai 训练并测试了一个推荐系统,且使用了其中一个整理好的数据集。

它是如何工作的……

在这一部分,你通过一个教程训练了一个非常基础的推荐系统。以下是你在这个教程中创建的关键 fastai 对象的总结:

  • 一个与 URLs 对象相关的 path 对象,用于存储精心整理的数据集:

    path = untar_data(URLs.ML_SAMPLE)
    
  • 一个 DataLoaders 对象:

    dls=CollabDataLoaders.from_df(df,bs= 64)
    
  • 一个 learner 对象:

    learn=collab_learner(dls, y_range= [ 0 , 5.0 ]
    

这些组成部分对你来说应该很熟悉,它们是你在第三章《使用表格数据训练模型》和第四章《使用文本数据训练模型》中使用的相同核心组成部分。fastai 高级 API 的一个优点是,它使用相同的构建模块来创建和训练适用于广泛数据集的深度学习模型,包括你在前面的章节和本节中见到的数据集。

这一部分有一部分不同于你到目前为止见过的大多数教程——在一组测试样本上测试已训练的深度学习模型。回忆一下你如何在第四章《使用文本数据训练模型》中测试语言模型的方式。以下是 text_model_training.ipynb 笔记本中测试语言模型的代码单元:

learn.predict("what comes next", n_words=20)

通过简单调用 predict() 函数,你可以从训练好的语言模型中获得结果,如下图所示:

图 5.7 – 在独立文本数据集上训练的语言模型的预测结果

图 5.7 – 在独立文本数据集上训练的语言模型的预测结果

我们将对比你在第四章《使用文本数据训练模型》中如何测试语言模型的方式,以及你在这个教程中训练推荐系统时需要做的工作,具体如下:

  1. 构建一个 test_df 数据框,包含测试样本,如下图所示:图 5.8 –  数据框的内容

    图 5.8 – test_df 数据框的内容

  2. 使用刚刚创建的test_df DataFrame 作为参数,调用test_dl。您可以把test_dl看作是在test_df DataFrame 上应用了与CollabDataLoaders对象dls相同的管道:

    dl = learn.dls.test_dl(test_df)
    

    这次调用的输出是测试数据加载器对象dl。通过这次对test_dl的调用,您已经将 DataFrame 转换成了训练好的模型(推荐系统)可以应用的格式。

  3. learn对象上调用get_preds,将训练好的推荐系统应用于数据加载器对象dl

    learn.get_preds(dl=dl)
    

为什么在训练推荐系统时需要额外的步骤,而在训练语言模型时不需要这些步骤?答案是,作为语言模型输入的文本短语不需要经过特定于样本的管道处理,就可以直接应用到模型中。

在后台,fastai 使用与TextDataLoaders对象相关的词汇表将输入字符串转换为标记(默认为单词),然后将标记转换为数字 ID。您不需要显式地调用这个管道——当您在语言模型上调用predict()时,这个管道会自动为您调用。

与输入语言模型的文本字符串不同,推荐系统的输入具有结构,因此您需要将推荐系统的输入样本定义为 DataFrame。一旦定义了 DataFrame,您还需要调用test_dl来应用转化管道(该管道在定义CollabDataLoaders对象时已隐式定义)到输入样本上。然后,您可以使用test_dl的输出从推荐系统中获取预测。

在进入下一个配方之前,值得更仔细地查看一下这个配方中的模型。由于对模型的详细描述超出了本书的范围,我们这里只关注一些重点内容。

如配方所示,模型被定义为collab_learner对象(文档在此:docs.fast.ai/collab.html#collab_learner)。这个对象是 fastai 的learner对象的一个特化,您在理解四大应用:表格、文本、推荐系统第一章**,快速上手 fastai中的图片部分首次见到过这个对象。

learn.model的输出显示该模型是一个EmbeddingDotBias模型(文档在此:docs.fast.ai/collab.html#EmbeddingDotBias)。

在大规模精选数据集上训练推荐系统

在一个小型精选数据集上训练推荐系统 部分,我们了解了如何创建推荐系统模型的基础知识。最终的系统还有一些不足,因为数据集只包含用户 ID 和电影 ID,因此无法确定哪些电影实际上被用户评价,模型也无法预测这些电影的评分。

在本节中,我们将创建一个推荐系统,解决之前推荐系统中的这个问题,因为它是基于一个包含电影标题的数据集训练的。和 ML_SAMPLE 数据集一样,我们将在本节中使用的数据集 ML_100k 也是来源于 MovieLens 数据集,但它包含了更多的记录和更丰富的特征。通过使用这个数据集创建推荐系统,我们将接触到 fastai 中用于摄取和处理推荐系统数据集的附加特性,并获得一个更有趣的训练推荐系统。

准备工作

确认你可以在仓库的 ch5 目录下打开 training_large_recommender_systems.ipynb 笔记本。

在本节的操作中,你将使用 tree 命令检查包含数据集的目录。如果你还没有在 Gradient 实例中安装 tree 命令,按照以下步骤安装它:

  1. 在 Gradient 终端中运行以下命令以准备安装 tree 命令:

    apt update
    
  2. 在 Gradient 终端中运行以下命令以安装 tree 命令:

    apt install tree
    

现在你已经可以在 Gradient 环境中使用 tree 命令,准备好按照本节中的步骤进行操作。

如何操作……

在本节中,你将运行 training_large_recommender_systems.ipynb 笔记本。一旦在 fastai 环境中打开笔记本,完成以下步骤:

  1. 运行笔记本中的单元格,直到 Ingest the dataset 单元格,以导入所需的库并设置笔记本。

  2. 运行以下单元格来定义该数据集的 path 对象:

    path = untar_data(URLs.ML_100k)
    
  3. 运行以下单元格以检查数据集的目录结构:

    path.ls()
    

    输出显示了数据集的目录结构,如 图 5.9 所示。请注意,数据集的结构比 ML_SAMPLE 更复杂:

    图 5.9 – ML_100k 数据集的 path.ls() 输出

    图 5.9 – ML_100k 数据集的 path.ls() 输出

  4. 你可以通过直接检查路径来了解构成数据集的文件。你可以通过运行 tree 命令来做到这一点。首先,在 Gradient 终端中运行以下命令,将数据集的根目录设置为当前目录:

    cd /storage/data/ml-100k
    
  5. 接下来,在 Gradient 终端中运行以下命令以列出目录的内容:

    tree command lists the contents of the directory:
    
    

    ├── README

    ├── allbut.pl

    ├── mku.sh

    ├── u.data

    ├── u.genre

    ├── u.info

    ├── u.item

    ├── u.occupation

    ├── u.user

    ├── u1.base

    ├── u1.test

    ├── u2.base

    ├── u2.test

    ├── u3.base

    ├── u3.test

    ├── u4.base

    ├── u4.test

    ├── u5.base

    ├── u5.test

    ├── ua.base

    ├── ua.test

    ├── ub.base

    └── ub.test

    
    We want to focus on the contents of two files from this set: `u.data`, which lists the ratings provided by users for movie IDs, and `u.item`, which lists details about the movies, including their titles.
    
  6. 运行以下单元格来定义df_data数据框,以包含u.data文件的内容。这个数据框将包含用户为电影提供的评分:

    df_data = pd.read_csv(path/'u.data', delimiter = '\t',
    header = None,\
    names = ['userId','movieId','rating','timestamp'])
    

    这个数据框定义是我们迄今为止看到的最复杂的。让我们逐个分析这些参数:

    a) path/'u.data':指定此数据框的来源,即u.data文件

    b) delimiter = '\t':指定制表符是此文件中分隔列的分隔符

    c) header = None:指定u.data文件的第一行不包含列名

    d) names = ['userId','movieId','rating','timestamp']:为数据框的列指定名称

  7. 运行以下单元格定义df_item数据框,以包含u.item文件的内容。这个数据框将包含关于电影的详细信息,包括它们的标题:

    df_item = pd.read_csv(path/'u.item', delimiter = '|',header = None,encoding = "ISO-8859-1")
    

    这是df_item定义的参数:

    a) path/'u.item':指定此数据框的来源,即u.item文件

    b) delimiter = '|':指定管道符号'|'是此文件中分隔列的分隔符

    c) header = None:指定此文件的第一行不包含列名

    d) encoding = "ISO-8859-1":指定用于读取文件的编码。如果不指定此编码,你将会遇到错误。

  8. 运行以下单元格查看你创建的df_data数据框的样本内容:

    df_data.head()
    

    该命令的输出显示数据框的前几行:

    图 5.10 – df_data 数据框的前几行

    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_5_10.jpg)

    图 5.10 – df_data 数据框的前几行

  9. 运行以下单元格查看df_data数据框的维度:

    df_data.shape
    

    该命令的输出显示df_data数据框的行数和列数:

    图 5.11 – df_data 数据框的形状

    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_5_11.jpg)

    图 5.11 – df_data 数据框的形状

  10. 运行以下单元格查看你创建的df_item数据框的样本内容:

    df_item.head()
    

    该命令的输出显示数据框的前几行:

    图 5.12 – df_item 数据框的前几行

  11. 运行以下单元格查看df_item数据框的维度:

    df_item DataFrame:![Figure 5.13 – The shape of the df_item DataFrame    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_5_13.jpg)Figure 5.13 – The shape of the df_item DataFrame
    
  12. 运行以下单元格准备df_item数据框,通过删除大部分列并为剩余的列添加列名:

    df_item = df_item.iloc[:,0:2]: Removes all the columns in the DataFrame except for the first two columns that contain the movie ID and the movie titleb) `df_item.columns = ['movieId','movieName']`: Assigns names to the columns in the remaining columns in the DataFramec) `df_item.head()`: Displays the first few rows of the transformed DataFrameThis cell produces as output the first few rows of the transformed DataFrame, as shown in *Figure 5.14*:![Figure 5.14 – The first few rows of the updated df_item DataFrame    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_5_14.jpg)Figure 5.14 – The first few rows of the updated df_item DataFrame
    
  13. 运行以下单元格将df_datadf_item数据框合并成一个新的数据框df,该数据框包含原始数据框的所有列:

    df =\
    pd.merge(df_data,df_item,on=['movieId'],how='left')
    df.head()
    

    这是merge用于合并数据框的参数:

    a) df_data:包含用户 ID、电影 ID 和评分的数据框。

    b) df_item:包含电影 ID 和电影标题的数据框。

    c) on=['movieId']:指定两个数据框将根据movieID列进行连接。

    d) how='left':指定从df_data数据框中提取的行将构成合并后新数据框的基础。也就是说,新数据框将与df_data具有相同的行数,每行包含df_data的所有列以及df_itemmovieName列。

    这个单元格输出的是新df数据框的前几行,如图 5.15所示。与df_data相比,您可以看到df有一个额外的列,movieName

    图 5.15 – df 的前几行

    图 5.15 – df 的前几行

  14. 运行以下单元格以查看df数据框的维度:

    df DataFrame. Note that df has the same number of rows as df_data:![Figure 5.16 – The shape of the df DataFrame    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_5_16.jpg)Figure 5.16 – The shape of the df DataFrame
    
  15. 运行以下单元格以查看df数据框中每列的唯一值数量:

    df.nunique()
    

    此命令的输出列出了数据框的所有列及每列中唯一项的数量:

    图 5.17 – df 数据框中每列的唯一值计数

    图 5.17 – df 数据框中每列的唯一值计数

  16. 运行以下单元格以检查df的任何列中是否存在缺失值。如果有缺失值,我们需要在训练推荐系统之前处理它们:

    df.isnull().sum()
    

    此命令的输出列出了每列缺失值的数量。由于没有缺失值,我们可以继续进行训练推荐系统的步骤:

    图 5.18 – df 数据框中每列缺失值的计数

    图 5.18 – df 数据框中每列缺失值的计数

  17. 运行以下单元格以为推荐系统定义一个CollabDataLoaders对象:

    dls=/
    CollabDataLoaders.from_df(df,item_name='movieName',bs= 64)
    

    以下是定义CollabDataLoaders对象的参数:

    a) df:用于创建CollabDataLoaders对象的数据框

    b) item_name='movieName':指定包含推荐系统主题项名称的列,在本例中为movieName

    c) bs= 64:设置批次大小(即计算平均损失的项目数)为 64

  18. 运行以下单元格以查看前一步中定义的CollabDataLoaders对象的一个批次:

    dls.show_batch()
    

    输出显示了批次的内容,如图 5.19所示:

    图 5.19 – show_batch 的输出

    图 5.19 – show_batch 的输出

  19. 运行以下单元格通过定义collab_learner对象来定义推荐系统的模型:

    learn=collab_learner(dls,y_range= [ 1 , 5 ] )
    

    以下是定义collab_learner对象的参数:

    a) dls:您为数据集定义的CollabDataLoaders对象。

    b) y_range= [ 1 , 5 ]:指定推荐系统预测的值的范围。在我们的例子中,这是数据集rating列中电影评分的范围。

  20. 运行以下单元格以训练推荐系统的模型:

    learn.fit_one_cycle( 5 )
    

    以下是模型定义的参数:

    a) 5:模型训练周期数

    输出显示了每个周期的训练和验证损失,如图 5.20所示。随着验证损失在各周期中的减少,你可以看到训练的改进:

    图 5.20 – 训练协同过滤模型的输出

    图 5.20 – 训练协同过滤模型的输出

  21. 在使用推荐系统之前,让我们先了解一下用户对一部口碑不太好的电影给出的评分。运行以下单元格,查看df数据框中电影《Showgirls》的子集:

    df_one_movie = df[df.movieName=='Showgirls (1995)']
    df_one_movie.head()
    

    输出显示了df数据框中包含《Showgirls》评分的前几行,如图 5.21所示:

    图 5.21 – 数据框中单部电影的子集

    图 5.21 – df数据框中单部电影的子集

  22. 从前一个单元格的输出来看,这部电影的评分范围似乎很广。让我们来看看所有用户对这部电影的平均评分是多少。运行以下单元格查看该电影的平均评分:

    df_one_movie['rating'].mean()
    

    输出如图 5.22所示。正如我们所预料的,这部电影的平均评分确实很低:

    图 5.22 – 《Showgirls》的平均评分

    图 5.22 – 《Showgirls》的平均评分

  23. 为了使用推荐系统,我们想要为一个用户的多部电影获取评分预测。运行以下单元格定义一个测试数据框,以便测试训练过的推荐系统:

    scoring_columns = ['userId','movieId','movieName']
    test_df = pd.DataFrame(columns=scoring_columns)
    test_df.at[0,'userId'] = 607
    test_df.at[0,'movieId'] = 242
    test_df.at[0,'movieName'] = 'Kolya (1996)'
    test_df.at[1,'userId'] = 607
    test_df.at[1,'movieId'] = 302
    test_df.at[1,'movieName'] = 'L.A. Confidential (1997)'
    test_df.at[2,'userId'] = 607
    test_df.at[2,'movieId'] = 375
    test_df.at[2,'movieName'] = 'Showgirls (1995)'
    test_df.head()
    

    这是此单元格的关键元素:

    a) scoring_columns:数据框的列名列表

    b) test_df:包含测试条目的数据框,列名在scoring_columns列表中指定

    注意,在这个数据框的每一行中,userId的值是相同的。当我们将这个数据框应用到推荐系统时,我们将为该用户的三部电影获取预测评分。

    test_df.head()的输出显示了完成数据框的内容,如图 5.23所示。对于这个数据框中的每一行,我们将使用推荐系统来预测userID607的用户对该行中电影的评分:

    图 5.23 –  数据框的内容

    图 5.23 – test_df 数据框的内容

  24. 运行以下单元格以从推荐系统获取test_df中条目的评分预测:

    dl = learn.dls.test_dl(test_df)
    learn.get_preds(dl=dl)
    

    此单元格的输出显示了推荐系统的结果,如下图所示:

图 5.24 – 推荐系统的结果

图 5.24 – 推荐系统的结果

这些结果意味着什么?推荐系统预测userID607的用户将给出以下评分:

a) 4.2557 对于《Kolya》

b) 4.3637 对于《L.A. Confidential》

c) 2.2996 对于《Showgirls》

这并不是对推荐系统的详尽测试,但评分似乎是可信的,因为对于被高度评价的电影《L.A. Confidential》,预测的评分较高;而对于获得非常差评价的电影《Showgirls》,预测的评分较低。

恭喜!你已经使用包含各种评分组合的大型整理数据集,在 fastai 中训练了一个推荐系统。

它是如何工作的…

在本节中,你通过一个操作在大型数据集上训练了推荐系统。值得总结一下这个操作与在小型整理数据集上训练推荐系统部分的操作有何不同。以下是关键区别:

  • ratings.csv。大数据集由 20 多个文件组成,尽管在本节中的操作中我们只使用了其中两个:u.data(包含用户 ID、电影 ID 以及用户对电影的评分)和u.item(包含有关电影的其他信息,包括其标题)。

  • 数据集的大小:小数据集包含略多于 6,000 条记录。大数据集包含 100k 条记录。

  • 测试数据框:对于在小数据集上训练的推荐系统,测试数据框包含我们希望获取预测评分的用户 ID 和电影 ID。对于在大数据集上训练的推荐系统,测试数据框包含用户 ID、电影 ID 以及我们希望获取预测评分的电影标题。

fastai 的功能使得在小型和大型整理数据集上创建推荐系统变得容易。在接下来的部分中,我们将探索如何使用 fastai 在独立数据集上创建推荐系统。

在独立数据集上训练推荐系统

在小型整理数据集上训练推荐系统在大型整理数据集上训练推荐系统的操作中,我们使用了两个来自 MovieLens 数据集的整理数据集,这些数据集包含了用户 ID、电影 ID 和用户对这些电影的评分。通过这两个整理数据集,我们创建了推荐系统,预测用户对特定电影的评分。

在本节中,我们将探索一个不属于 fastai 整理数据集范围的数据集。我们将在本节中使用的数据集是亚马逊产品数据集(www.kaggle.com/saurav9786/amazon-product-reviews)。该数据集包含了亚马逊上各种产品的用户评分。

在本节中,我们将使用数据集的一个子集,即与电子产品相关的子集。在本节中的操作中,我们将导入这个独立数据集,并利用它来训练一个推荐系统,预测用户对某个商品的评分。

准备工作

确认你能够打开位于仓库ch5目录中的training_recommender_systems_on_standalone_dataset.ipynb笔记本。

请确保通过以下步骤已将亚马逊产品数据集文件上传到您的 Gradient 环境:

  1. www.kaggle.com/saurav9786/amazon-product-reviews下载archive.zip文件。

  2. 解压下载的archive.zip文件,提取ratings_Electronics (1).csv。将此文件重命名为ratings_Electronics.csv

  3. 在您的 Gradient 环境中的终端,从/storage/archive设置为当前目录:

    cd /storage/archive
    
  4. 创建/storage/archive/amazon_reviews目录:

    mkdir amazon_reviews
    
  5. /storage/archive/amazon_reviews设置为当前目录:

    cd /storage/archive/amazon_reviews
    
  6. 将您在步骤 2中解压的文件(ratings_Electronics.csv)上传到/storage/archive/amazon_reviews。您可以按照以下步骤使用 Gradient 中 JupyterLab 的上传按钮进行上传:

    a) 在您的 Gradient 环境中的终端,将/notebooks设置为当前目录:

    notebooks/temp directory, make a new /notebooks/temp directory:
    
    

    临时选择当前文件夹,点击上传按钮(参见图 5.25),然后从您在步骤 2中解压的本地系统文件夹中选择ratings_Electronics.csv文件:

    
    

图 5.25 – JupyterLab 中的上传按钮

图 5.25 – JupyterLab 中的上传按钮

d) 从您的 Gradient 环境中的终端,将ratings_Electronics.csv文件复制到/storage/archive/amazon_reviews目录:

cp /notebooks/temp/ratings_Electronics.csv /storage/archive/amazon_reviews/ratings_Electronics.csv

我衷心感谢能够使用亚马逊产品数据集,展示 fastai 在非精选数据集上的推荐系统能力。

数据集引用

J. McAuley, C. Targett, J. Shi, A. van den Hengel (2015)。基于图像的风格和替代品推荐cseweb.ucsd.edu/~jmcauley/pdfs/sigir15.pdf)。信息检索特别兴趣小组(SIGIR 2015)

现在您已将数据集文件复制到 Gradient 环境中,您已准备好按照步骤进行操作。

如何操作…

在本节中,您将运行training_recommender_systems_on_standalone_dataset.ipynb笔记本。一旦您在 fastai 环境中打开该笔记本,按照以下步骤完成:

  1. 运行笔记本中的单元格直到导入数据集单元格,以导入所需的库并设置您的笔记本。

  2. 运行以下单元格以定义该数据集的路径对象:

    path = URLs.path('amazon_reviews')
    

    参数'amazon_reviews'表示正在定义此路径对象以指向您在准备阶段中创建的/storage/archive/amazon_reviews目录。

  3. 运行以下单元格以检查数据集的目录结构:

    path.ls()
    

    输出显示数据集的目录结构,如图 5.26所示:

    图 5.26 – 亚马逊产品数据集的输出

    图 5.26 – 亚马逊产品数据集的path.ls()输出

  4. 运行以下单元格以定义df数据框,包含ratings_Electronics.csv文件的内容:

    df = pd.read_csv(path/'ratings_Electronics.csv',header = None)
    

    以下是定义 df 数据框的参数:

    a) path/'ratings_Electronics.csv':指定该数据框的来源,即 ratings_Electronics.csv 文件

    b) header = None:指定该文件的第一行不包含列名

  5. 运行以下单元格,为 df 数据框的列定义名称。这些列名称来自于数据集的描述:www.kaggle.com/saurav9786/amazon-product-reviews

    df.columns = ['userID','productID','rating','timestamp']
    
  6. 运行以下单元格检查 df 数据框:

    df.head()
    

    输出,如图 5.26所示,列出了数据集中的记录。每条记录表示一个用户对某个产品的评分。userID 列包含用户的 ID,productID 列包含产品的 ID,rating 列包含用户对该产品的评分:

    图 5.27 –  输出

    图 5.27 – df.head() 输出

  7. 运行以下单元格获取 df 数据框的维度:

    df.shape
    

    输出,如图 5.27所示,显示了数据框(DataFrame)有超过 700 万行:

    图 5.28 – 数据框 df 的形状

    图 5.28 – 数据框 df 的形状

  8. 运行以下单元格定义 CollabDataLoaders 对象 dls

    dls=CollabDataLoaders.from_df(df,bs= 64)
    

    以下是定义 CollabDataLoaders 对象 dls 的参数:

    a) df:指定你为数据集创建的 df 数据框用于创建 CollabDataLoaders 对象

    b) bs = 64:指定批量大小为 64

  9. 运行以下单元格查看你在前一步定义的 CollabDataLoaders 对象中的一批数据:

    dls.show_batch()
    

    输出显示了该批次的内容,如图 5.28所示:

    图 5.29 –  输出

    图 5.29 – show_batch() 输出

  10. 运行以下单元格定义用于推荐系统模型的 collab_learner 对象:

    learn=collab_learner(dls,y_range= [ 0 , 5.0 ] )
    

    以下是定义 collab_learner 对象的参数:

    a) dls:你在前一步定义的 CollabDataLoaders 对象

    b) y_rangerating 列中值的范围

  11. 运行以下单元格训练推荐系统模型:

    learn.fit_one_cycle( 1 )
    

    这是模型定义的参数:

    a) 1:模型训练过程中使用的 epoch 数量。

    注意

    你在本例中使用的数据集很大。回想一下,当你运行获取 df 数据框维度的单元格时,你发现该数据框有超过 700 万行。这意味着训练模型将需要一些时间。例如,在一个 Gradient 环境中,我花费了超过 3 小时才在这个数据集上训练了推荐系统模型。

    输出显示了训练和验证的损失,如以下截图所示:

    图 5.30 – 训练协同过滤模型的输出

    图 5.30 – 训练协同过滤模型的输出

  12. 运行以下单元格以定义 test_df 数据框,其中包含一些你可以用来测试已训练的推荐系统的测试项:

    scoring_columns = ['userID','productID']
    test_df = pd.DataFrame(columns=scoring_columns)
    test_df.at[0,'userID'] = 'A2NYK9KWFMJV4Y'
    test_df.at[0,'productID'] = 'B008ABOJKS'
    test_df.at[1,'userID'] = 'A29ZTEO6EKSRDV'
    test_df.at[1,'productID'] = 'B006202R44'
    test_df.head()
    

    下面是该单元格的关键元素:

    a) scoring_columns:数据框的列名列表。请注意,这些列名与你在 图 5.29show_batch() 输出中看到的列名相同。

    b) test_df:包含测试项的数据框,列名由 scoring_columns 列表中指定。

    test_df.head() 的输出显示了完成的 test_df 数据框的内容,如下图所示:

    图 5.31 –  数据框的内容

    图 5.31 – test_df 数据框的内容

  13. 现在,你已经定义了 test_df 数据框,可以使用它来测试已训练的推荐系统。运行以下单元格以获取推荐系统对 test_df 中条目的评分预测:

    dl = learn.dls.test_dl(test_df)
    learn.get_preds(dl=dl)
    

    该单元格的输出显示了推荐系统的结果,如下图所示:

图 5.32 – 推荐系统的结果

图 5.32 – 推荐系统的结果

这些结果意味着什么?

a) 输出中的第一项,4.4364,是推荐系统对 test_df 中第一项的预测。推荐系统预测,用户 ID 为 A2NYK9KWFMJV4Y 的用户会为产品 ID 为 B008ABOJKS 的产品打出 4.4364 的评分。

b) 输出中的第二项,2.5531,是推荐系统对 test_df 中第二项的预测。推荐系统预测,用户 ID 为 A29ZTEO6EKSRDV 的用户会为产品 ID 为 B006202R44 的产品打出 2.5531 的评分。

恭喜!你已经使用 fastai 在独立数据集上训练了推荐系统。

它是如何工作的…

在这一部分,你完成了一个在独立数据集上训练推荐系统的教程。与第三章《使用表格数据训练模型》和第四章《使用文本数据训练模型》中的独立数据集类似,本节使用的独立数据集要求你比使用 fastai 精心挑选的数据集时多做一些额外的步骤,这些步骤见于《在小型精心挑选数据集上训练推荐系统》和《在大型精心挑选数据集上训练推荐系统》教程中。

下面是独立数据集所需额外步骤的总结:

  1. 找到一个数据集。这听起来可能很简单,但一旦你超越了 fastai 提供的精选数据集,并尝试使用独立的数据集进行学习时,找到合适的数据集就可能成为一大挑战。Kaggle 网站,www.kaggle.com/,是一个很好的起点。你需要一个足够大的数据集,以便在训练深度学习模型时有更好的机会。

    我的经验是,如果我不使用迁移学习(即,从一个已经在通用数据集上训练过的预先存在的模型开始,这个数据集适用于我想要训练的深度学习模型的应用场景),那么我需要有一个至少包含数万条数据项的数据集。

    你需要的数据集不宜过大。正如你在本节的食谱中看到的,以及你在第四章中训练的语言模型中看到的,使用文本数据训练模型,用一个大数据集训练模型可能需要几个小时。如果你使用的是需要付费的 Gradient 实例(而不是 Colab 或免费的 Gradient 实例),你可能会为一次训练花费几美元。如果你需要进行多次训练,费用可能会累积。

  2. 将数据集导入到你的 fastai 环境中。正如本食谱中准备工作部分所描述的那样,在你将数据集下载到本地系统后,你需要在 fastai 环境中创建一个目录来存放数据集文件,然后将文件上传到该目录。

fastai 的一个优势是提供了大量精选数据集。然而,对于像推荐系统这样的数据集类型,fastai 只提供一两个精选数据集,在这种情况下,掌握寻找具有不同特点的独立数据集的能力是很有优势的,这样你就可以进行更多种类的实验。通过遵循本节中的建议,你将能够在准备好进一步学习时,拓宽你的数据集视野,超越 fastai 提供的精选数据集。

测试你的知识

现在你已经完成了一些扩展的 fastai 推荐系统训练示例,你可以尝试一些变体来巩固你所学到的内容。

准备工作

确保你已经按照在独立数据集上训练推荐系统的食谱进行了操作。在本节中,你将根据该食谱中操作的笔记本,调整并为一个新的独立数据集创建推荐系统。

如何做…

你可以按照本节中的步骤尝试一些你在在独立数据集上训练推荐系统食谱中用 Amazon 产品数据集训练的推荐系统的变体:

  1. 复制你在 Training a recommender system on a standalone dataset 这个教程中操作过的 training_recommender_systems_on_standalone_dataset.ipynb 笔记本。给你新复制的笔记本命名为:training_recommender_systems_on_new_standalone_dataset.ipynb

  2. 查看整个 Amazon 产品数据集的描述,访问 jmcauley.ucsd.edu/data/amazon/。从 small subsets for``experimentation 列表中,选择一个除 Electronics 外的类别。你已经在 Training a recommender system on a standalone dataset 这个教程中使用 Electronics 数据集训练过推荐系统,所以你需要选择另一个类别。

    例如,你可以选择 Office ProductsAutomotive。我建议避免选择 Books 类别,因为它的文件大小是 Electronics 数据集的三倍,这样你可能需要整整一天来训练一个 Books 数据集的推荐系统。

  3. 修改 Training a recommender system on a standalone dataset 教程中 Getting ready 部分的步骤,获取你在 Step 2 中选择的类别的 ratings only 数据集,并将其加载到你的 fastai 环境中。

  4. 更新你的 training_recommender_systems_on_new_standalone_dataset.ipynb 笔记本,将你在 Step 3 中导入到 fastai 环境中的数据集加载进来。

  5. 更新你的笔记本,加载数据集到 DataFrame 中,并使用你在本章中学到的技巧(包括 head()shape)来检查数据集的结构。

  6. 运用你在 Step 5 中学到的知识,更新你新创建的 training_recommender_systems_on_new_standalone_dataset.ipynb 笔记本,构建、训练并测试你在 Step 2 中选择的数据集的推荐系统。

恭喜!你已经完成了使用 fastai 训练推荐系统的复习。

第六章:第六章:使用视觉数据训练模型

深度学习已经成功应用于许多不同类型的数据,包括表格数据、文本数据和推荐系统数据。在第三章 使用表格数据训练模型第四章 使用文本数据训练模型第五章 训练推荐系统 中,你已经见识过 fastai 对这些类型数据的处理方法。这些数据类型都是深度学习故事的一部分,但视觉数据图像数据是传统上与深度学习最紧密相关的数据类型。

视觉数据也是 fastai 框架最全面支持的数据类型。fastai 的高级 API 主要是为视觉数据开发的,70%的精选 fastai 数据集都是视觉数据集。在本章中,我们将探索 fastai 为探索视觉数据集和构建高性能深度学习图像模型所提供的一些功能。

在本章中,你将学习如何使用 fastai 提供的一套丰富功能来准备图像数据集,并使用这些数据集训练深度学习模型。特别是,你将学习如何创建 fastai 深度学习模型来分类图像,即确定图像中的对象是什么,同时也将学习如何使用 fastai 来识别同一图像中的多个对象。

本章将涵盖以下内容:

  • 使用简单精选视觉数据集训练分类模型

  • 探索精选图像位置数据集

  • 使用独立视觉数据集训练分类模型

  • 使用精选视觉数据集训练多图像分类模型

  • 测试你的知识

技术要求

请确保你已经完成了第一章 快速入门 fastai 中的设置部分,并且已经成功设置了 Gradient 实例或 Colab 环境。本章中的食谱假设你正在使用 Gradient。请确保你已经从github.com/PacktPublishing/Deep-Learning-with-fastai-Cookbook克隆了本书的代码库,并且能够访问ch6文件夹。该文件夹包含本章中描述的代码示例。

使用简单精选视觉数据集训练分类模型

你可能还记得你在第一章中训练的第一个 fastai 模型,快速入门 fastai。那个模型是使用 MNIST 手写数字数据集训练的。给定一张手写数字的图像,那个模型能够对图像进行分类,即确定图像中显示的是从 0 到 9 的哪个数字。

在本教程中,你将应用在 MNIST 模型中看到的相同方法,使用另一个 fastai 精选数据集:CIFAR 数据集。这个数据集是更大 CIFA_100 数据集的一个子集,包含 6000 张图像,按 10 个类别进行组织。在本节中,你训练的模型将能够确定来自该数据集的图像所属的类别。

准备工作

确认你可以在仓库的 ch6 目录中打开 training_with_curated_image_datasets.ipynb 笔记本。

注意事项

CIFAR 数据集中的图像相当小。在本节中,我们将它们以更大的尺寸呈现,以便在书中的上下文中更容易识别,但结果是它们看起来可能有些模糊。

本节中介绍的 CIFAR 数据集在论文 Learning Multiple Layers of Features from Tiny Image(Krizhevsky,2009)中有所介绍。我很感激能有机会将这个数据集包含在本书中。

数据集引用

Alex Krizhevsky. (2009). Learning Multiple Layers of Features from Tiny Image. University of Toronto: www.cs.toronto.edu/~kriz/learning-features-2009-TR.pdf.

如何操作…

在本节中,你将运行 training_with_curated_image_datasets.ipynb 笔记本。一旦你在 fastai 环境中打开该笔记本,请完成以下步骤:

  1. 运行笔记本中的单元格,直到 Ingest the dataset 单元格,以导入所需的库并设置笔记本环境。

  2. 运行以下单元格以定义此数据集的 path 对象:

    path = untar_data(URLs.CIFAR)
    
  3. 运行以下单元格以检查数据集的目录结构:

    path.ls()
    

    输出显示了数据集的目录结构,如下截图所示:

    图 6.1 – path.ls() 的输出

    图 6.1 – path.ls() 的输出

  4. 运行以下单元格以为此数据集定义一个 ImageDataLoaders 对象:

    dls = ImageDataLoaders.from_folder(path, train='train', valid='test')
    

    以下是定义 ImageDataLoaders 对象的参数:

    a) path:指定 ImageDataLoaders 对象是通过你在前一个单元格中创建的 path 对象定义的

    b) train='train':指定训练数据位于 /storage/data/cifar10/train 目录中

    c) valid='test':指定验证数据位于 /storage/data/cifar10/test 目录中

  5. 运行以下单元格以显示数据集中的一批图像:

    dls.train.show_batch(max_n=4, nrows=1)
    

    该单元格的输出是一组 4 项来自一批数据,显示图像及其对应的类别,如下图所示:

    图 6.2 – show_batch() 的输出

    图 6.2 – show_batch() 的输出

  6. 运行以下单元格以检查 train 子目录的内容:

    (path/'train').ls()
    

    输出显示了 train 子目录的结构,如下截图所示:

    图 6.3 – train 子目录的内容

    图 6.3 – train 子目录的内容

  7. 运行以下单元格检查train/dog子目录的内容:

    (path/'train/dog').ls()
    

    输出显示了train/dog子目录的结构:

    图 6.4 – 子目录的内容

    图 6.4 – train/dog子目录的内容

  8. 如果你想从不同的角度查看该数据集的目录结构,可以使用tree命令。为此,在 Gradient 终端中输入以下命令:

    test and train directories have subdirectories for each of the 10 categories of the dataset:
    
    

    ├── test

    │   ├── airplane

    │   ├── automobile

    │   ├── bird

    │   ├── cat

    │   ├── deer

    │   ├── dog

    │   ├── frog

    │   ├── horse

    │   ├── ship

    │   └── truck

    └── train

    ├── airplane

    ├── automobile

    ├── bird

    ├── cat

    ├── deer

    ├── dog

    ├── frog

    ├── horse

    ├── ship

    └── truck

    
    
  9. 运行以下单元格查看数据集中的单个项目:

    img_files = get_image_files(path)
    img = PILImage.create(img_files[100])
    img
    

    以下是该单元格的关键元素:

    a) img_files = get_image_files(path):指定path将被递归地检查,并返回路径中所有的图片文件。如果你想了解更多关于get_image_files的细节,可以查看以下文档:docs.fast.ai/data.transforms.html#get_image_files

    b) img = PILImage.create(img_files[100]):从先前语句返回的特定文件创建图像对象img

    该单元格的输出是数据集文件之一,在笔记本中呈现为图像,如下所示:

    图 6.5 – 数据集中的一张图片

    图 6.5 – 数据集中的一张图片

  10. 运行以下单元格显示数据集中的另一张图片:

    img = PILImage.create(img_files[3000])
    img
    

    输出是数据集中的另一张图像,已在笔记本中呈现如下:

    图 6.6 – 数据集中的另一张图片

    图 6.6 – 数据集中的另一张图片

  11. 运行以下单元格将模型定义为cnn_learner对象:

    learn = cnn_learner(dls, resnet18, 
                        loss_func=LabelSmoothingCrossEntropy(), 
                        metrics=accuracy))
    

    以下是定义cnn_learner对象的参数:

    a) dls:指定模型使用你在步骤 4中定义的ImageDataLoaders对象dls

    b) resnet18:指定使用预训练模型resnet18作为该模型的起点。

    c) loss_func=LabelSmoothingCrossEntropy():指定训练过程中使用的损失函数。

    d) metrics=accuracy:指定accuracy为训练过程中优化的性能度量标准。

  12. 运行以下单元格训练模型:

    learn.fine_tune(5)
    

    该参数表示训练将进行5个周期。

    输出显示了每个周期的训练损失、验证损失和准确率,如下截图所示:

    图 6.7 – 模型训练输出

    图 6.7 – 模型训练输出

  13. 让我们尝试使用训练好的模型对测试数据集中的一些示例进行预测。首先,运行以下单元格为测试数据集中的一张图片定义一个对象:

    img_test_files = get_image_files(path/"test")
    img2 = PILImage.create(img_test_files[700])
    img2
    

    以下是该单元格的关键元素:

    a) img_files = get_image_files(path/"test"):返回test目录下的所有图像文件

    b) img = PILImage.create(img_files[700]):从前一个语句返回的特定文件创建图像对象img2

    这个单元格的输出是一张狗的图像:

    图 6.8 – 测试数据集中的狗图像

    图 6.8 – 测试数据集中的狗图像

  14. 运行以下单元格,为测试数据集中的另一张图像定义一个对象:

    img3 = PILImage.create(img_test_files[8000])
    img3
    

    这个单元格的输出是一张鸟类图像:

    图 6.9 – 测试数据集中的鸟类图像

    图 6.9 – 测试数据集中的鸟类图像

  15. 现在我们已经为测试数据集中的几张图像定义了对象,让我们在这些图像上运行训练好的图像分类模型。首先,运行以下单元格,将模型应用于狗的图像:

    learn.predict(img2)
    

    这个单元格的输出是模型的预测,见下图。注意,模型正确地预测了图像的类别:

    图 6.10 – 图像分类模型在狗图像上的预测

    图 6.10 – 图像分类模型在狗图像上的预测

  16. 现在让我们看看模型在鸟类图像上的表现。运行以下单元格来将模型应用于鸟类图像:

    learn.predict(img3)
    

    这个单元格的输出是模型的预测,见下图。注意,模型正确地预测了这张图像是鸟类:

    图 6.11 – 图像分类模型在鸟类图像上的预测

    图 6.11 – 图像分类模型在鸟类图像上的预测

  17. 现在我们已经成功地训练了模型,让我们保存它。运行以下单元格来保存模型:

    learn.path = Path('/notebooks/temp')
    learn.export('cifar_apr20_2021.pkl') 
    

    下面是这个单元格中语句的作用:

    a) learn.path = Path('/notebooks/temp'):将learn对象的路径设置为可以写入的目录。请记住,在 Gradient 中,默认情况下,learn对象的路径是只读的,因此你需要将路径调整为可写目录,才能保存模型。

    b) learn.export('cifar_apr20_2021.pkl'):指定保存的模型名称为cifar_apr20_2021.pkl

    在 Gradient 中运行此单元格后,你的模型将保存到/notebooks/temp/model/cifar_apr20_2021.pkl

恭喜!你已经使用精心挑选的图像数据集,训练并执行了一个 fastai 图像分类模型。

它是如何工作的……

在本节的配方中,有一些重要的内容值得回顾。在这一部分,我们将讨论一些在配方主干部分可能不太明显的细节。

标签在目录名和文件名中进行编码

首先,考虑构成数据集的文件名。train/dog子目录中的文件如下所示:

图 6.12 – 子目录中的文件

图 6.12 – train/dog子目录中的文件

train/cat子目录中的文件如下所示:

图 6.13 – train/cat 子目录中的文件

图 6.13 – train/cat 子目录中的文件

让我们来看一下这些子目录中每个目录的一个示例文件。要显示来自train/dog子目录的图像文件,请运行以下单元格:

dog_files = get_image_files(path/"train/dog")
dog_img = PILImage.create(dog_files[30])
dog_img

该单元格的输出确实是一张狗的图像,如下所示:

图 6.14 – 来自训练数据集的狗的图像

图 6.14 – 来自训练数据集的狗的图像

要显示来自train/cat子目录的图像文件,请运行以下单元格:

cat_files = get_image_files(path/"train/cat")
cat_img = PILImage.create(cat_files[30])
cat_img

该单元格的输出,如预期所示,是一张猫的图像,如下所示:

图 6.15 – 来自训练数据集的猫的图像

图 6.15 – 来自训练数据集的猫的图像

对于这个图像分类问题,模型正试图预测图像的标签,即图像中所示对象的类别,例如狗、鹿或鸟。对于这个数据集,标签以以下两种方式编码:

  • 包含图像文件的目录名称。如你在图 6.14图 6.15中看到的那样,图像的标签是通过图像所在子目录的名称进行编码的。

  • xxxx_dog.png的文件名,猫的图像文件名则为xxxx_cat.png

你使用迁移学习训练了图像分类模型

第四章中,使用文本数据训练模型,我们使用迁移学习将现有的训练模型调整为适应特定用例的数据集。你可能没有注意到,但我们在这个示例中做了同样的事情。线索就在定义cnn_learner对象的单元格中,具体如下:

learn = cnn_learner(dls, resnet18, 
                    loss_func=LabelSmoothingCrossEntropy(), 
                    metrics=accuracy))

cnn_learner的定义中,resnet18参数是作为图像分类模型基础的预训练模型。第一次运行此单元格时,你将看到类似图 6.16中显示的消息,指示模型正在你的环境中设置:

图 6.16 – 第一次运行 cnn_learner 定义时你收到的消息

图 6.16 – 第一次运行 cnn_learner 定义时你收到的消息

第二个线索在训练模型的单元格中,具体如下:

learn.fine_tune(5)

对于到目前为止你在本书中看到的大多数模型,你使用了learn.fit_one_cycle()来训练模型。这里,由于我们想要更新一个现有的模型以适应我们的特定用例,因此我们使用learn.fine_tune()。这正是我们在第四章中所做的,使用文本数据训练模型,我们使用迁移学习在预训练的AWD_LSTM模型上训练语言模型。

为什么我们在这个用例中使用迁移学习而不是从头开始训练模型?简单的答案是,通过使用迁移学习,我们能够更快地获得模型的良好表现。你可以通过对 training_with_curated_image_datasets.ipynb 笔记本做两个修改并重新运行来亲自试试看。以下是进行此操作的步骤:

  1. 更新 cnn_learner 对象的定义,添加 pretrained=False 参数,如下所示:

    learn = cnn_learner(dls, resnet18, pretrained=False,
                        loss_func=LabelSmoothingCrossEntropy(), metrics=accuracy)
    

    这一更改意味着,模型将使用 CIFAR 数据集从头开始训练,而不是利用预训练模型。

  2. 将训练语句更改为以下内容:

    learn.fit_one_cycle(5)
    

在做出这些更改后重新运行笔记本时,这个新模型在分类图像时不会表现得很好。迁移学习是有效的,正如 Howard 和 Gugger 在他们的书中解释的那样,它在深度学习的标准介绍中并没有得到足够的关注。幸运的是,fastai 被设计成易于利用迁移学习的强大功能,正如你在本食谱的 How to do it… 部分训练的图像分类模型所展示的那样。

更仔细地审视模型

在继续下一个食谱之前,值得更仔细地看看本食谱中的模型。由于本书的范围限制,模型的详细描述将超出本书内容,我们将在这里关注一些重点。

如食谱中所示,模型被定义为 cnn_learner 对象(文档链接:docs.fast.ai/vision.learner.html#cnn_learner),使用的是预训练的 resnet18 模型(文档链接:pytorch.org/vision/stable/models.html)。learn.summary() 输出显示该模型包括一系列卷积层及其相关层。有关卷积神经网络(CNN)的描述,请参见:machinelearningmastery.com/how-to-develop-a-convolutional-neural-network-from-scratch-for-mnist-handwritten-digit-classification/

探索一个精心策划的图像位置数据集

第二章《使用 fastai 探索和清理数据》中,我们介绍了使用 fastai 导入和探索多种数据集的过程。

在本节中,我们将探索一个特别策划的图像数据集,名为COCO_TINY。这是一个CIFAR数据集,我们在使用简单策划视觉数据集训练分类模型教程中使用过,它在每张图片中只有一个带标签的物体,而图像位置数据集中的图片则带有边界框(指示图片中物体的位置)以及物体的名称。此外,COCO_TINY数据集中的图片可以包含多个带标签的物体,如下所示:

图 6.17 – 来自图像位置数据集的标注图片

图 6.17 – 来自图像位置数据集的标注图片

在本节的教程中,我们将引入数据集并应用其注释信息,创建该数据集的dataloaders对象。每张图片的注释信息可能相当复杂。每张图片可以包含多个带标签的物体。每个带标签的物体的注释信息包括图片的文件名、围绕物体的边界框的xy坐标,以及物体类别的标签。例如,图 6.17中展示的图片包含了几个沙发物体和一个椅子物体。

请注意,数据集中的图片实际上并不展示注释。数据集中的典型图片如下所示:

图 6.18 – 来自 COCO_TINY 数据集的原始图片

图 6.18 – 来自 COCO_TINY 数据集的原始图片

图片本身没有边界框或标签。这些注释信息包含在一个单独的文件中。本节的教程将展示如何将图片文件与注释信息结合起来。

在本教程的最后,当我们使用show_batch()显示包含注释的批次样本时,图片中会显示边界框和标签。例如,图 6.19展示了当通过show_batch()显示图 6.18中的图片时的效果——现在你可以看到图片中的花瓶物体的边界框和标签:

图 6.19 – 图 6.18 中图片的注释版

图 6.19 – 图 6.18 中图片的注释版

在本节的教程中,你将结合COCO_TINY数据集中的图片文件,例如图 6.18中展示的图片,与注释文件中的信息,以获得一个dataloaders对象,该对象包含所有带标签物体的图片、边界框和标签,如图 6.19所示。

准备开始

确认你可以打开位于你的仓库中ch6目录下的exploring_image_location_datasets.ipynb笔记本。

本教程中用于检查数据集的方法包括受这个 Kaggle 内核启发的方法,https://www.kaggle.com/jachen36/coco-tiny-test-prediction,演示了如何使用 fastai 探索图像位置数据集。

本节中介绍的COCO_TINY数据集出自论文《Microsoft COCO: Common Objects in Context》,Lin 等人,2014 年。我很感激能够在本书中使用这个数据集作为示例。

数据集引用

Tsung-Yi Lin, Michael Maire, Serge Belongie, Lubomir Bourdev, Ross Girshick, James Hays, Pietro Perona, Deva Ramanan, C. Lawrence Zitnick 和 Piotr Dollár. (2014). Microsoft COCO: Common Objects in Context: arxiv.org/abs/1405.0312.

如何操作…

在本节中,您将运行exploring_image_location_datasets.ipynb笔记本。将该笔记本在您的 fastai 环境中打开后,完成以下步骤:

  1. 运行笔记本中的单元,直到导入数据集单元,以导入所需的库并设置笔记本环境。

  2. 运行以下单元来为此数据集定义path对象:

    path = untar_data(URLs.COCO_TINY)
    
  3. 运行以下单元以检查数据集的目录结构:

    path.ls()
    

    输出显示了数据集的目录结构,如图 6.20所示:

    图 6.20 – path.ls() 的输出

    图 6.20 – path.ls() 的输出

    数据集由train子目录中的一组图像文件和train.json注释文件组成。在本食谱的接下来的步骤中,我们将更详细地查看train.json文件的内容。

  4. 运行以下单元将train.json文件导入为一系列 Python 字典:

    with open(path/'train.json') as json_file:
        data = json.load(json_file)
        # each nested structure is a list of dictionaries
        categories = data['categories']
        images = data['images']
        annotations = data['annotations']  
    

    以下是该单元中使用的代码的关键部分:

    a) data = json.load(json_file):将整个train.json文件的内容加载到data字典中。

    b) categories = data['categories']:为类别定义创建一个单独的字典列表。该字典定义了图像中的对象。

    c) images = data['images']:为图像文件创建一个单独的字典列表。

    d) annotations = data['annotations']:为边界框创建一个单独的字典列表。

  5. 运行以下单元查看前一个单元中创建的每个字典的结构:

    print("categories ", categories)
    print()
    print("subset of images",list(images)[:5])
    print()
    print("subset of annotations",list(annotations)[:5])
    

    该单元的输出显示了每个字典内容的示例,如下图所示:

    图 6.21 – 注释字典的内容

    图 6.21 – 注释字典的内容

  6. 我们在前一步创建的字典并不是我们所需要的,无法为图像提供完整的注释。我们需要的是每个图像的汇总注释,列出图像中每个对象的边界框及其类别。我们可以通过操作前面创建的字典手动完成这个任务,但 fastai 提供了一个非常方便的函数get_annotations,它可以为我们完成这个工作。运行以下单元来定义注释结构:

    image_files, bbox_lbl = get_annotations(path/'train.json')
    img_bbox_combo = dict(zip(image_files, bbox_lbl))
    

    以下是该单元中代码的关键部分:

    a) aget_annotations(path/'train.json'):对train.json文件应用get_annotations函数,以获取注释结构。该函数的输出是文件名列表和标记边界框列表。

    b) dict(zip(image_files, bbox_lbl)):创建一个字典,将文件列表和从前一个命令输出的标记边界框列表结合起来。

  7. 运行以下单元格以检查在前一个单元格中创建的注释字典元素之一,img_bbox_combo

    img_bbox_combo[image_files[5]]
    

    此单元格的输出,如下图所示,表明字典的元素是由包含边界框列表(定义对象周围区域的 2 个xy坐标,分别表示左上角和右下角点)和对应对象类别的列表组成的元组:

    图 6.22 – img_bbox_combo 的一个元素

    图 6.22 – img_bbox_combo 的一个元素

  8. 运行以下单元格以查看与前一个单元格中检查的注释相关的图像:

    image_subpath = 'train/'+image_files[5]
    img = PILImage.create(path/image_subpath)
    img
    

    此单元格的输出是以下图像:

    图 6.23 – 示例图像

    图 6.23 – 示例图像

  9. 运行以下单元格以定义一个函数,返回与输入图像文件相关的边界框:

    def get_bbox(filename):
        return np.array(img_bbox_combo[os.path.basename(filename)][0])
    
  10. 运行以下单元格以定义一个函数,返回与输入图像文件相关的标签(即类别):

    def get_lbl(filename):
        return np.array(img_bbox_combo[os.path.basename(filename)][1],dtype=object)
    
  11. 运行以下单元格以定义一个函数,返回数据集中的图像文件:

    def get_items(noop):
        return get_image_files(path/'train')
    
  12. 运行以下单元格以使用你在前面三个单元格中定义的函数,为数据集定义一个DataBlock对象:

    db = DataBlock(blocks=(ImageBlock, BBoxBlock, BBoxLblBlock),
                     get_items=get_image_files,
                     splitter=RandomSplitter(),
                     get_y=[get_bbox, get_lbl],
                     n_inp=1)
    

    迄今为止,在本书的几乎所有配方中,我们都使用了顶层的 fastai API,这意味着在此步骤中我们会定义某种类型的dataloaders对象。然而,对于此数据集,我们需要比dataloaders对象提供的更大的灵活性,这就是为什么我们在这里定义了一个DataBlock对象。有关DataBlock对象的详细信息,请参考以下文档:https://docs.fast.ai/data.block.html#DataBlock。

    以下是DataBlock对象定义的参数:

    a) blocks=(ImageBlock, BBoxBlock, BBoxLblBlock):指定模型的输入是图像(ImageBlock),目标有两个部分:图像中对象的边界框(BBoxBlock),以及与每个边界框相关的标签(类别)(BBoxLblBlock)。

    b) get_items=get_image_files:指定调用get_image_files函数以获取DataBlock对象的输入。

    c) get_y=[get_bbox, get_lbl]:指定应用于get_items结果的函数。图像文件名作为参数传递给这些函数。第一个函数get_bbox根据我们从train.json文件中导入的注释信息,返回与图像文件关联的边界框列表。第二个函数get_lbl根据我们从train.json文件中导入的注释信息,返回与图像文件关联的边界框的标签(类别)列表。

    d) n_inp=1:指定在blocks参数中定义的元组中应视为输入的元素数量,在我们的情况下,仅为图像文件。

  13. 运行以下单元格,使用你在前一个单元格中创建的DataBlock对象db来定义dataloaders对象:

    dls = db.dataloaders(path,bs=32)
    

    这是dataloaders定义的参数:

    a) path:指定用于dataloaders对象的路径是你在笔记本开始时为数据集定义的path对象

    b) bs=32:指定批次大小为 32

  14. 现在终于可以看到dataloaders对象的批次样子了。运行以下单元格,查看一个批次中的条目示例:

    dls.show_batch(max_n=4, figsize=(10,10))
    

    这是show_batch()的参数:

    a) max_n=4:指定要显示的样本数量

    b) figsize=(10,10):指定样本的尺寸

    show_batch()的输出显示在下图中。注意,你会看到带有边界框和注释文本的标注物体的图像。同时注意,你可能会在笔记本中运行show_batch()时看到不同的示例图像:

图 6.24 – show_batch()输出的示例图像

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_6_24.jpg)

图 6.24 – show_batch()输出的示例图像

恭喜你!你已经成功地导入并准备了图像位置数据集,这是本书中你将要处理的最复杂的数据操作任务之一。

它是如何工作的……

在本节的操作中,可能会对COCO_TINY数据集和图像位置数据集的一般情况有一些问题。在本节中,我们将解答一些你在操作过程中可能会遇到的问题。

数据集中的图像标注了哪些类型的物体?

如果你多次运行以下单元格,你会看到多种标注图像:

dls.show_batch(max_n=4, figsize=(10,10))

你可以在图 6.25中看到此单元格输出的示例。注意,注释并没有覆盖图像中的每个可能物体。例如,第一个图像中的动物没有被注释。在COCO_TINY中的图像会注释哪些物体?我们将在本节中回答这个问题:

图 6.25 – show_batch()的输出

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_6_25.jpg)

图 6.25 – show_batch()的输出

当你运行以下单元格将注释文件train.json引入 Python 字典时,你创建了一个名为categories的子字典,其中包含COCO_TINY数据集中所有注释物体的类别,如下所示:

with open(path/'train.json') as json_file:
    data = json.load(json_file)
    # each nested structure is a list of dictionaries
    categories = data['categories']
    images = data['images']
    annotations = data['annotations']  

要查看categories字典中的内容,请运行以下单元格:

print("categories ", categories)

该单元格的输出如下:

图 6.26 – 类别字典

图 6.26 – 类别字典

从这个字典中,你可以看到COCO_TINY数据集中的图像只有六个注释物体:椅子沙发电视遥控器,和花瓶

如果你反复运行show_batch(),你将看到即使是图像中仅部分显示的物体也会被注释。例如,图 6.25中的第一张图像就有一个椅子的注释,如下图所示,尽管图像中只显示了椅子部分的腿:

图 6.27 – 椅子注释的特写

图 6.27 – 椅子注释的特写

你还将看到一些图像包含许多注释物体,并且这些物体的边界框可能会重叠,如下图所示:

图 6.28 – 一张包含多个重叠边界框的图像

图 6.28 – 一张包含多个重叠边界框的图像

本小节描述了COCO_TINY中哪些物体被注释。在你多次运行show_batch()命令时,你将亲眼看到某些物体的注释是多么复杂。

注释物体的边界框是如何定义的?

当你在本节中按步骤操作时,可能会问自己边界框是在哪里定义的。当你运行以下单元格将注释文件train.json引入 Python 字典时,你创建了一个名为annotations的子字典,其中包含COCO_TINY数据集中所有物体的注释:

with open(path/'train.json') as json_file:
    data = json.load(json_file)
    # each nested structure is a list of dictionaries
    categories = data['categories']
    images = data['images']
    annotations = data['annotations']  

你可以运行以下单元格来查看annotations字典子集的内容:

print("subset of annotations",list(annotations)[:5])

该单元格的输出显示了特定图像中物体注释的示例,如下截图所示:

图 6.29 – 注释示例

图 6.29 – 注释示例

该字典中bbox键的值通过指定物体边界的* x y *值来定义物体的边界框。

请看下图。这张图像包含一个注释的物体——让我们看看如何查看其边界框的坐标:

图 6.30 – 带有边界框的注释示例图像

图 6.30 – 带有边界框的注释示例图像

这张图像的文件名是000000071159.jpg。要查看此图像的边界框,请运行以下单元格:

get_bbox(path/'train/000000071159.jpg')

此单元格的输出显示了该图像的边界框坐标。如果图像中有多个对象被注释,那么每个对象都会定义一个边界框:

图 6.31 – 图 6.30 中示例图像的对象边界框

图 6.31 – 图 6.30 中示例图像的对象边界框

在这一部分中,我们回顾了 COCO_TINY 数据集中边界框注释的一些细节。

为什么我们没有使用 COCO_TINY 来训练模型?

与本书中大多数食谱不同,本部分的食谱并没有包括模型训练。在我们为 COCO_TINY 数据集创建 dataloaders 对象之后,为什么我们没有继续训练模型呢?

简单的答案是,fastai 框架目前并未提供一种简单的方法来训练像 COCO_TINY 这样的图像位置数据集。如果你想尝试训练这样的模型,可以参考这个仓库中的方法,但要准备好深入细节,超出我们在本书中探索的高层 fastai API:github.com/muellerzr/Practical-Deep-Learning-for-Coders-2.0/blob/master/Computer%20Vision/06_Object_Detection.ipynb

使用独立视觉数据集训练分类模型

使用简单的策划视觉数据集训练分类模型 这一食谱中,你学习了如何导入 fastai 策划数据集并使用它来训练图像分类模型。

在这一部分,你将通过相同的过程处理一个独立的数据集,叫做 fruits-360。该数据集(详细描述见这里:https://www.kaggle.com/moltean/fruits)包含了超过 90,000 张水果和蔬菜的图像,分为超过 130 个类别。

在本食谱中,我们将首先将该数据集导入 Gradient。然后,我们将通过 training_with_standalone_image_datasets.ipynb 笔记本来导入数据集并用它来训练一个 fastai 图像分类模型。最后,我们将查看训练好的模型如何分类测试集中的图像,并保存训练好的模型。

准备工作

确保你已经按照以下步骤将 fruits-360 数据集上传到你的 Gradient 环境中:

  1. www.kaggle.com/moltean/fruits 下载 archive.zip

  2. archive.zip 上传到你的 Gradient 环境中。你可以使用 Gradient 中 JupyterLab 的上传按钮进行上传,但需要按照以下几个步骤进行操作:

    a) 在 Gradient 环境的终端中,将 /notebooks 设为当前目录:

    notebooks/temp directory, make a new /notebooks/temp directory:
    
    

    临时切换到当前文件夹,选择上传按钮(参见 图 6.32),然后从你在 步骤 1 中下载的本地系统文件夹中选择 archive.zip图 6.32 – JupyterLab 中的上传按钮    图 6.32 – JupyterLab 中的上传按钮

    
    
  3. 现在你已经将archive.zip上传至 Gradient 环境中的/notebooks/temp目录,通过在 Gradient 终端运行以下命令,将该目录设为当前目录:

    cd /notebooks/temp
    
  4. 通过在 Gradient 终端运行以下命令,将archive.zip解压到/storage/archive目录:

    unzip archive.zip -d /storage/archive
    
  5. 通过在 Gradient 终端中运行以下命令,确认你现在已经在 Gradient 环境中设置了数据集:

    cd /storage/archive/fruits-360
    
  6. 然后,运行以下命令列出该目录的内容:

    ls
    

    此命令的输出应与以下截图所示的内容类似:

图 6.33 – fruits-360 目录的内容

图 6.33 – fruits-360 目录的内容

通过这些准备步骤,你已将fruits-360数据集的文件移动到 Gradient 环境中的正确位置,以便 fastai 模型使用。

本节中展示的fruits-360数据集来源于 2020 年 Kaggle 比赛Fruits 360www.kaggle.com/moltean/fruits)。非常感谢有机会在本书中使用这个数据集作为示例。

数据集引用

Mihai Oltean(2017-2020)。Fruits 360 – 包含 131 种水果和蔬菜的 90,380 张图片的数据集 (mihaioltean.github.io)。

如何操作…

在本节中,你将运行training_with_standalone_image_datasets.ipynb笔记本。一旦在你的 fastai 环境中打开该笔记本,完成以下步骤:

  1. 在笔记本中运行单元格,直到Ingest the dataset单元格,以导入所需的库并设置笔记本。

  2. 运行以下单元格,为此数据集定义path对象:

    path = URLs.path('fruits-360')
    
  3. 运行以下单元格检查数据集的目录结构:

    path.ls()
    

    输出显示数据集的目录结构,如以下截图所示:

    图 6.34 – path.ls()的输出

    图 6.34 – path.ls()的输出

  4. 运行以下单元格,为此数据集定义一个ImageDataLoaders对象:

    dls = ImageDataLoaders.from_folder(path, train='Training', valid='Test')
    

    以下是ImageDataLoaders对象定义的参数:

    a) path:指定使用你在前一个单元格中创建的path对象来定义ImageDataLoaders对象

    b) train='Training':指定训练数据位于/storage/archive/fruits-360/Training目录中

    c) valid='Test':指定验证数据位于/storage/archive/fruits-360/Test目录中

  5. 运行以下单元格显示数据集的一批数据:

    dls.train.show_batch(max_n=4, nrows=1)
    

    此单元格的输出是一组来自一批的4个项目,显示了图片及其对应的类别,如以下截图所示:

    图 6.35 – show_batch()的输出

    图 6.35 – show_batch()的输出

  6. 运行以下单元格以检查Training子目录的内容:

    (path/'Training').ls() 
    

    输出显示了Training子目录的结构,如下图所示:

    图 6.36 – Training子目录的内容

  7. 运行以下单元格以查看数据集中的单个项目:

    img_files = get_image_files(path)
    img = PILImage.create(img_files[100])
    img
    

    以下是此单元格的关键要素:

    a) img_files = get_image_files(path): 指定递归地检查path并返回该路径中的所有图像文件

    b) img = PILImage.create(img_files[100]): 从前述语句返回的特定文件创建图像对象img

    该单元格的输出是数据集文件之一,并在笔记本中渲染为图像:

    图 6.37 – 来自数据集的一张图像

    图 6.37 – 来自数据集的一张图像

  8. 运行以下单元格来将模型定义为一个cnn_learner对象:

    learn = cnn_learner(dls, resnet18, 
                        loss_func=LabelSmoothingCrossEntropy(), 
                        metrics=accuracy))
    

    以下是cnn_learner对象定义的参数:

    a) dls: 指定使用你在本笔记本中之前定义的ImageDataLoaders对象来定义cnn_learner对象

    b) resnet18: 指定用于该模型的预训练模型作为起点

    c) loss_func=LabelSmoothingCrossEntropy(): 指定在训练过程中使用的损失函数

    d) metrics=accuracy: 指定accuracy作为训练过程中优化的性能指标

  9. 运行以下单元格来训练模型:

    learn.fine_tune(5)
    

    该参数表示训练将运行5个时期(epoch)。

    输出显示每个时期的训练损失、验证损失和准确率,如下图所示:

    图 6.38 – 训练模型的输出

    图 6.38 – 训练模型的输出

  10. 让我们在测试数据集中的一些示例上尝试训练好的模型。首先,运行以下单元格为测试数据集中的一张图像定义一个对象:

    img_test_files = get_image_files(path/"Test")
    img2 = PILImage.create(img_test_files[700])
    img2
    

    以下是此单元格的关键要素:

    a) img_files = get_image_files(path/"Test"): 返回Test目录下的所有图像文件

    b) img = PILImage.create(img_files[700]): 从前述语句返回的特定文件创建图像对象img2

    该单元格的输出是草莓的图像,如下图所示:

    图 6.39 – 测试数据集中的一张草莓图像

    图 6.39 – 测试数据集中的一张草莓图像

  11. 运行以下单元格为测试数据集中的另一张图像定义一个对象:

    img3 = PILImage.create(img_test_files[8000])
    img3
    

    该单元格的输出是番茄的图像,如下图所示:

    图 6.40 – 测试数据集中的一张番茄图像

    图 6.40 – 测试数据集中的一张番茄图像

  12. 现在我们已经为测试数据集中的几张图像定义了对象,让我们在这些图像上测试训练好的图像分类模型。首先,运行以下单元格来应用模型到草莓图像:

    learn.predict(img2)
    

    本单元格的输出是模型的预测结果,如下图所示。请注意,模型正确预测了图像的类别。TensorImage 数组中显示的数字对应训练模型认为该图像属于每个类别的可能性:

    图 6.41 – 图像分类模型对草莓图像的预测

    图 6.41 – 图像分类模型对草莓图像的预测

  13. 让我们看看模型在番茄图像上的表现。运行以下单元格,将模型应用于番茄图像:

    learn.predict(img3)
    

    本单元格的输出是模型的预测结果,如下图所示。请注意,模型正确预测了图像是番茄:

    图 6.42 – 图像分类模型对鸟类图像的预测

    图 6.42 – 图像分类模型对鸟类图像的预测

  14. 训练好的模型似乎在预测测试集中的图像类别时表现良好,但仍然存在一些模糊性。水果和蔬菜的图像可能对人类来说是模糊的,因此让我们看看训练好的模型如何预测那些我们明确知道类别的图像。首先,运行以下单元格,定义来自测试数据集的鳄梨图像:

    avocado_files = get_image_files(path/"Test/Avocado")
    avocado_img = PILImage.create(avocado_files[30])
    avocado_img
    

    下面是本单元格的关键元素:

    a) avocado_files = get_image_files(path/"Test/Avocado"):返回 Test/Avocado 目录下的所有图像文件

    b) avocado_img = PILImage.create(avocado_files[30]):从先前语句返回的文件集中的特定鳄梨图像文件创建图像对象 avocado_img

    本单元格的输出是鳄梨的图像,如下图所示:

    图 6.43 – 来自测试数据集的鳄梨图像

    图 6.43 – 来自测试数据集的鳄梨图像

  15. 让我们从测试数据集中的另一个目录获取一张图像。运行以下单元格,定义来自测试数据集的胡桃图像:

    walnut_files = get_image_files(path/"Test/Walnut")
    walnut_img = PILImage.create(walnut_files[30])
    walnut_img
    

    下面是本单元格的关键元素:

    a) walnut_files = get_image_files(path/"Test/Walnut"):返回 Test/Walnut 目录下的所有图像文件

    b) walnut_img = PILImage.create(walnut_files[30]):从先前语句返回的文件集中的特定胡桃图像文件创建图像对象 walnut_img

    本单元格的输出是胡桃的图像,如下图所示:

    图 6.44 – 来自测试数据集的胡桃图像

    图 6.44 – 来自测试数据集的胡桃图像

  16. 现在我们已经为来自测试数据集中某些特定目录的几张图像定义了对象,让我们在这些图像上运用训练好的图像分类模型。首先,运行以下单元格,将模型应用于鳄梨图像:

    learn.predict(avocado_img)
    

    该单元格的输出是模型对avocado_img图像的预测结果,如下截图所示。请注意,模型正确预测了图像的类别:

    图 6.45 – 图像分类模型对鳄梨图像的预测

    图 6.45 – 图像分类模型对鳄梨图像的预测

  17. 让我们看看模型在胡桃图像上的表现。运行以下单元格,将模型应用于胡桃图像:

    learn.predict(walnut_img) 
    

    该单元格的输出是模型的预测结果,如下截图所示。请注意,模型正确预测了图像为胡桃:

    图 6.46 – 图像分类模型对胡桃图像的预测

    图 6.46 – 图像分类模型对胡桃图像的预测

  18. 现在我们已经测试了模型,确认它能够对少量图像做出准确预测,运行以下单元格来保存模型:

    learn.save("fruits_model"+modifier)
    

    该单元格的输出确认模型已保存在数据集路径的models子目录中,如下截图所示:

图 6.47 – 保存模型的输出

图 6.47 – 保存模型的输出

恭喜!你已经在一个大型独立图像数据集上训练了一个 fastai 图像分类模型,并使用该数据集中的一组示例对训练后的模型进行了测试。

它是如何工作的…

如果你将使用简单整理的视觉数据集训练分类模型的配方与本节中的配方进行对比,你会注意到代码非常相似。实际上,一旦你将fruits-360数据集导入到你的 Gradient 环境中,training_with_standalone_image_datasets.ipynb笔记本中的代码与training_with_curated_image_datasets.ipynb笔记本中的代码非常接近。然而,仍然有一些不同之处,如下所示:

  • 路径定义语句有所不同。对于整理数据集,路径定义语句的参数是URLs对象:

    path = untar_data(URLs.CIFAR)
    

    而对于独立数据集,路径语句的参数是fruits-360目录:

    path = URLs.path('fruits-360')
    
  • 测试集和训练集目录在两个数据集之间有不同的名称。

  • 独立数据集的规模远大于整理数据集,无论是在图像数量还是图像分类数目上。

  • 默认情况下,独立数据集的训练模型在准确度上(超过 95%)要高于整理数据集(约 80%)。

尽管存在这些差异,但值得注意的是,fastai 能够如此轻松地训练一个模型来对fruits-360数据集中的图像进行分类。正如你在本节中的配方所看到的那样,你能够在fruits-360数据集上训练模型,并且只用几行代码就能获得出色的性能。我认为你在本节中创建的模型是 fastai 强大灵活性的一个绝佳例子。

使用策划的视觉数据集训练多图像分类模型

使用简单策划的视觉数据集训练分类模型食谱中,你已经完成了加载 fastai 策划数据集并使用它训练图像分类模型的步骤。

在本节中,你将为另一个名为PASCAL_2007的策划数据集进行相同的操作。这个数据集(在这里有更详细的描述:http://host.robots.ox.ac.uk/pascal/VOC/)包含大约 5000 张训练图像和相同数量的测试图像。该数据集包括标注,标识出每张图像中出现的常见物体。标识的物体来自 20 个类别,包括动物(牛、狗、猫、羊和马)、交通工具(船、公交车、火车、飞机、自行车和汽车)以及其他物品(人、沙发、瓶子和电视显示器)。

使用简单策划的视觉数据集训练分类模型食谱中引入的CIFAR数据集中的图像只有一个标注的物体。相比之下,PASCAL_2007数据集中的图像可以包含零个、一个或多个标注的物体。正如你将在本节中看到的,训练一个能够预测同一图像中多个物体的模型是存在一些挑战的。

在本节中,我们将加载PASCAL_2007数据集,包括图像注释,探索数据集,训练一个 fastai 模型,根据图像中描绘的物体对图像进行分类,然后在测试数据集中的一些示例上测试训练好的模型。

准备工作

确认你可以在 repo 的ch6目录中打开training_with_curated_multi_image_classification_datasets.ipynb笔记本。

本节中介绍的PASCAL_2007数据集在以下文献中介绍:PASCAL 视觉物体类别(VOC)挑战http://host.robots.ox.ac.uk/pascal/VOC/pubs/everingham10.pdf。非常感谢能够在本书中包含使用该数据集的示例。

数据集引用

Mark Everingham、Luc Van Gool、Christopher K. I. Williams、John Winn 和 Andrew Zisserman(2008 年)。PASCAL 视觉物体类别(VOC)挑战 (http://host.robots.ox.ac.uk/pascal/VOC/pubs/everingham10.pdf)。

如何操作…

在本节中,你将运行training_with_curated_multi_image_classification_datasets.ipynb笔记本。一旦你在 fastai 环境中打开了这个笔记本,完成以下步骤:

  1. 运行笔记本中的代码单元,直到加载数据集单元,以导入所需的库并设置笔记本环境。

  2. 运行以下代码单元,为此数据集定义path对象:

    path = untar_data(URLs.PASCAL_2007)
    
  3. 运行以下代码单元,检查数据集的目录结构:

    path.ls()
    

    输出显示了数据集的目录结构,如下图所示:

    图 6.48 – path.ls()的输出

    图 6.48 – path.ls()的输出

    现在让我们回顾一下PASCAL_2007数据集中我们将在本食谱中使用的目录结构中的关键项:

    a) /storage/data/pascal_2007/train:包含用于训练模型的图像的目录

    b) /storage/data/pascal_2007/test:包含用于测试训练模型的图像的目录

    c) /storage/data/pascal_2007/train.json:包含训练图像注释的文件

    d) /storage/data/pascal_2007/test.json:包含测试图像注释的文件

  4. 运行以下单元格,将train.json文件中的注释转化为 Python 对象:

    with open(path/'train.json') as json_file:
        data = json.load(json_file)
        # each nested structure is a list of dictionaries
        categories = data['categories']
        images = data['images']
        annotations = data['annotations']
    

    下面是此单元格中使用的代码的关键部分:

    a) data = json.load(json_file):将整个train.json文件的内容加载到data字典中。

    b) categories = data['categories']:创建一个仅用于类别定义的字典列表。这个字典定义了数据集中图像中的对象。

    c) images = data['images']:创建一个仅用于图像文件的字典列表。

    d) annotations = data['annotations']:创建一个仅用于图像中对象及其边界框的注释字典列表。

    正如你将在本教程的后续步骤中看到的那样,我们不会使用categoriesimagesannotations字典列表来提取注释以供 fastai 模型训练过程使用。相反,我们将使用 fastai 的内置 API,get_annotations,直接处理注释。然而,我们在这个单元格中定义的字典,将有助于我们理解关于注释的细节。

  5. 运行以下单元格,查看一些注释示例:

    print("categories ", categories)
    print()
    print("subset of images",list(images)[:5])
    print()
    print("subset of annotations",list(annotations)[:5])
    

    本单元格的输出列出了你在前一个单元格中创建的每个字典列表的条目。如下面的截图所示,categories字典列表包含了图像中可能出现的对象类别及其对应的 ID:

    图 6.49 – 类别字典列表的条目

    图 6.49 – 类别字典列表的条目

    以下截图显示,images字典列表列出了训练集中的图像文件名,以及它们的 ID 和尺寸:

    图 6.50 – 图像字典列表的条目

    图 6.50 – 图像字典列表的条目

    以下截图显示,annotations列出了给定image_id的图像中出现的对象的边界框和类别 ID:

    图 6.51 – 注释字典列表的条目

    图 6.51 – 注释字典列表的条目

  6. 我们将使用 fastai 函数get_annotations来获取给定图像所需的所有注释信息,也就是图像中所描绘对象的类别。运行以下单元格以定义所需的注释结构:

    image_files, bbox_lbl = get_annotations(path/'train.json')
    img_bbox_combo = dict(zip(image_files, bbox_lbl))
    

    下面是此单元格中使用的代码的关键部分:

    a) get_annotations(path/'train.json'):将get_annotations函数应用于train.json文件以获取注释结构。此函数的输出是一个文件名列表,以及每个图像中每个对象的边界框和对象类别列表。以下截图显示了bbox_lbl的示例内容,指定了三个对象的边界框和类别:

    图 6.52 – 示例边界框和类别

    图 6.52 – 示例边界框和类别

    b) dict(zip(image_files, bbox_lbl)):创建一个字典,将文件列表和从前一个命令输出的标注边界框列表结合起来。

  7. 现在,让我们来看看训练集中的一个图像文件及其注释。首先,运行以下单元格查看特定图像文件的注释:

    img_bbox_combo[image_files[5]]
    

    输出显示与该图像文件相关联的注释:

    图 6.53 – 图像文件的注释

    图 6.53 – 图像文件的注释

  8. 运行以下单元格查看我们在前一个单元格中检查过注释的图像:

    image_subpath = 'train/'+image_files[5]
    img = PILImage.create(path/image_subpath)
    img
    

    输出显示与image_files[5]相关联的图像。如以下图所示,该图像确实包含三架飞机,这也在前一个单元格中看到的注释中有所指示:

    图 6.54 – 标注表明包含三架飞机的图像

    图 6.54 – 标注表明包含三架飞机的图像

  9. 运行以下单元格以定义get_category函数,从您之前创建的字典列表中提取值。我们稍后将使用此函数来检查测试集中的图像注释:

    def get_category(in_key_value,in_key,out_key,dict_list):
        return([cat[out_key] for cat in dict_list if cat[in_key]==in_key_value] )
    
  10. 运行以下单元格以定义get_lbl函数,该函数以文件名为输入,并返回该文件注释结构中的类别名称列表:

    def get_lbl(filename):
        return np.array(img_bbox_combo[os.path.basename(filename)][1],dtype=object)
    

    下面是这个函数的关键部分:

    a) os.path.basename(filename):返回完整路径filename的最后部分。例如,如果filename是完整路径/storage/data/pascal_2007/train/006635.jpg,则os.path.basename返回006635.jpg。此转换是必需的,因为输入的图像文件将包含完整路径,但img_bbox_combo结构仅使用文件名的最后部分进行索引。

    b) img_bbox_combo[os.path.basename(filename)][1]:返回与filename图像文件相关联的类别。

  11. 运行以下单元格查看get_lbl如何工作的示例:

    get_lbl('/storage/data/pascal_2007/train/007911.jpg') 
    

    输出,如以下截图所示,是一个包含与/storage/data/pascal_2007/train/006635.jpg图像文件相关的类别的 NumPy 数组:

    图 6.55 – 的示例输出

    图 6.55 – get_lbl的示例输出

  12. 看起来我们在前一个单元格中作为get_lbl参数使用的图像上发生了很多事情。运行以下单元格查看图像:

    image_subpath = 'train/007911.jpg'
    img = PILImage.create(path/image_subpath)
    img
    

    如下图所示,这张图片与我们在前一个单元格中看到的注释相匹配。它确实包含了一辆摩托车和一群人:

    图 6.56 – 一张包含摩托车和几个人的图片

    图 6.56 – 一张包含摩托车和几个人的图片

  13. 运行以下单元格来检查此数据集中训练集和测试集中文件的数量:

    print("number of training images: ",len(get_image_files(path/'train')))
    print("number of testing images: ",len(get_image_files(path/'test')))
    

    输出,如下图所示,显示了每个集合中的文件数量:

    图 6.57 – 训练集和测试集中文件数量

    图 6.57 – 训练集和测试集中文件数量

  14. 运行以下单元格来查看数据集中的类别数量:

    print("number of categories is: ",len(categories))
    

    输出,如下图所示,显示了数据集中的类别数量:

    图 6.58 – 数据集中的类别数量

    图 6.58 – 数据集中的类别数量

  15. 运行以下单元格来定义get_items函数,该函数将在DataBlock定义中用于获取输入数据文件:

    def get_items(noop):
        return_list = []
        empty_list = []
        # filter the training files and keep only the ones with valid info in the JSON file
        for file_path in get_image_files(path/'train'):
            file_id_list = get_category(os.path.basename(file_path),'file_name','id',images)
            if len(file_id_list) > 0:
                return_list.append(file_path)
            else:
                empty_list.append(file_path)
        print("len(return_list): ",len(return_list))
        print("len(empty_list): ",len(empty_list))
        return(return_list)
    

    以下是此函数定义中的关键项:

    a) return_list = []:初始化一个包含带注释的训练集图像文件的列表。

    b) empty_list = []:初始化一个包含没有注释的训练集图像文件的列表。

    c) for file_path in get_image_files(path/'train'):遍历训练集中的文件。

    d) file_id_list:是与当前file_path对应的注释中的文件 ID 列表。

    e) if len(file_id_list) > 0:检查file_id_list是否有条目。如果有,函数将当前file_path添加到return_list中。否则,函数将当前file_path添加到empty_list中。

    f) return(return_list):此函数只返回带注释的图像文件子集。如果包括任何没有注释的图像文件,在定义dataloaders对象的步骤中会发生错误。

  16. 运行以下单元格来为数据集定义一个DataBlock对象:

    db = DataBlock(blocks=(ImageBlock, MultiCategoryBlock),
                     get_items = get_items,               
                     splitter=RandomSplitter(),
                     get_y=[get_lbl],
                     item_tfms = RandomResizedCrop(128,\
    min_scale=0.35),
                     n_inp=1)
    

    以下是DataBlock对象定义中的关键项:

    a) blocks=(ImageBlock, MultiCategoryBlock):指定输入数据的类型(图像文件),并表明数据集具有多类别标签,即每张图片可以包含多个标注对象。

    b) get_items = get_items:指定用于获取输入项的函数;在此情况下,是我们在前一个单元格中定义的get_items函数,它返回所有带有注释的训练集文件。

    c) splitter=RandomSplitter():告诉 fastai 从训练集中随机选择项目来创建验证集,默认使用训练集中的 20%的项目。

    d) get_y=[get_lbl]:指定获取输入标签的函数,在此情况下为get_lbl函数。该函数以文件名为输入,返回该文件注释中的类别列表。

    e) item_tfms = RandomResizedCrop(168, min_scale=0.3):指定训练过程中应用的转换。由于训练集中的图像文件大小不同,我们需要将它们转换为统一的大小,否则会在show_batch中出现错误。此转换通过裁剪图像来调整其大小,使其具有相同的尺寸。

    f) n_inp=1:指定在定义的blocks子句中应视为输入的元素,在此情况下是1,即仅为ImageBlock

  17. 运行以下单元格以使用你在前一个单元格中创建的DataBlock对象db来定义dataloaders对象:

    dls = db.dataloaders(path,bs=32)
    

    这是dataloaders定义的参数:

    a) path:指定dataloaders对象的源是你在笔记本中之前创建的path对象。

    b) bs=32:指定批处理大小为32

    此单元格的输出,如下图所示,显示了return_list(包含有效注释的图像文件列表)和empty_list(没有有效注释的图像文件列表)中元素的数量:

    图 6.59 – dataloader 定义输出

    图 6.59 – dataloader 定义输出

  18. 运行以下单元格以显示一组来自批次的样本:

    dls.show_batch(max_n=4, figsize=(10,10))
    

    输出显示来自训练集的图像及其对应图像文件注释中描述的物体类别,如下图所示:

    图 6.60 – show_batch 的结果

    图 6.60 – show_batch 的结果

  19. 运行以下单元格以通过指定cnn_learner对象来定义模型:

    learn = cnn_learner(dls, resnet18)
    

    这是cnn_learner定义的参数:

    a) dls:指定模型使用你在前一个单元格中定义的dataloaders对象进行训练。

    b) resnet18:指定模型基于预训练的resnet18模型。

  20. 运行以下单元格以训练模型10个 epoch:

    learn.fine_tune(10)
    

    输出列出了每个 epoch 的训练和验证损失,如下图所示。随着验证损失的降低,模型的性能得到了提升:

    图 6.61 – 多类别图像分类模型训练输出

    图 6.61 – 多类别图像分类模型训练输出

  21. 现在我们已经训练好了模型,让我们用一些来自测试集的图像来测试它。首先,运行以下单元格以准备并显示测试集中的一张图像:

    img_test_files = get_image_files(path/"test")
    img2 = PILImage.create(img_test_files[100])
    img2
    

    此单元格显示以下图像:

    图 6.62 – 来自测试集的图像

    图 6.62 – 来自测试集的图像

  22. 运行以下单元格,将训练好的模型应用于上一单元格中显示的图像:

    learn.predict(img2)
    

    从输出结果中,如下截图所示,你可以看到模型正确地分类了图像中的一个物体,但对图像中的第二个物体的分类是错误的:

    图 6.63 – 模型对马图像的预测

    图 6.63 – 模型对马图像的预测

  23. 运行以下单元格来准备并显示来自测试集的另一张图像:

    img3 = PILImage.create(img_test_files[200])
    img3
    

    这个单元格显示了以下图像:

    图 6.64 – 来自测试集的图像

    图 6.64 – 来自测试集的图像

  24. 运行以下单元格,将训练好的模型应用于上一单元格中显示的图像:

    learn.predict(img3)
    

    从输出结果中,如下截图所示,你可以看到模型正确地分类了图像中的物体:

图 6.65 – 模型对猫图像的预测

图 6.65 – 模型对猫图像的预测

恭喜你!你已经训练了一个 fastai 模型,能够对图像中的多个物体进行分类。

它是如何工作的……

本节中的配方是本书中最长、最复杂的配方之一。以下是一些你在完成步骤时可能没有注意到的配方方面。

本配方使用了迁移学习

如果你查看步骤 19步骤 20中的代码,你会看到一个类似于模型定义和训练步骤的模式,跟使用简单精心策划的视觉数据集训练分类模型配方和使用独立视觉数据集训练分类模型配方中的步骤相似。

在所有这些配方中,你在模型定义中指定了一个预训练模型,然后使用fine_tune语句对模型进行训练。利用这个预训练的视觉分类模型对于本配方是非常合理的,因为我们的训练集相对较小,正如你将在下一部分看到的那样。

本配方的训练集对于深度学习模型来说比较小

这个配方的训练集有多大?初始训练集稍微超过了 5,000 张图像,如你在步骤 13中看到的训练集数量:

图 6.66 – 训练集和测试集中项目的数量

图 6.66 – 训练集和测试集中项目的数量

然而,在步骤 15中,我们定义了get_items函数时,必须过滤训练图像列表,只保留那些具有有效类别标注的图像。从步骤 17的输出中可以看到,我们定义了dataloaders对象并调用了get_items函数,输入的训练图像中不到一半具有有效的标注:

图 6.67 – return_list 和 empty_list 中的项目数量

图 6.67 – return_list 和 empty_list 中的项目数量

这意味着什么?这意味着我们有 2,500 张图像来训练我们的模型。只有 2,500 张图像来训练一个复杂的模型,会产生影响,正如我们将在下一节看到的那样。

这个配方训练的模型表现并不出色

我们在本节中的配方中训练的模型表现并不突出。它通常能正确识别图像中的一个物体,但往往无法识别多个物体,而且当它能识别第二个或第三个物体时,通常会将这些物体分配到错误的类别。为什么这个模型的表现不好呢?

在这个配方中,我们只使用了 2,500 张图像来训练一个复杂的模型,将图像中的多个物体分类为 20 个类别。与使用独立视觉数据集训练分类模型配方中使用的fruits-360数据集相比,后者有超过 90,000 张图像。要查看训练集中有多少文件,可以在你的 Gradient 环境中运行以下命令:

find /storage/archive/fruits-360/Training -type f | wc -l

以下截图显示了该命令的输出:

图 6.68 – 训练集中的图像数量

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_6_68.jpg)

图 6.68 – fruits-360训练集中的图像数量

fruits-360数据集的训练集包含超过 67,500 个文件。这意味着,在本章中我们看到的第一个精心策划的图像数据集问题中,我们的训练样本是本节配方的 25 倍,并且在第一个实例中,我们将模型应用于识别每张图像中的单一物体这一相对简单的问题。PASCAL_2007数据集中训练图像的相对不足,可能部分解释了我们用这个数据集训练的模型表现平平。

测试你的知识

在 fastai 明确支持的四个应用领域(表格、文本、推荐系统和图像/视觉)中,fastai 为使用图像数据集创建模型提供了最全面的支持。在本章中,我们仅仅触及了 fastai 和图像数据集的部分应用。接下来,你将有机会更深入地了解本章中的 fastai 图像数据集配方。

准备工作

确保你已经完成了使用精心策划的视觉数据集训练多图像分类模型配方。在本节中,你将根据该配方中的笔记本,尝试对图像数据集进行深度学习的新的变动。

如何做……

你可以按照本节中的步骤,尝试对你用PASCAL_2007数据集训练的图像分类模型做一些变动,参考使用精心策划的视觉数据集训练多图像分类模型配方:

  1. 创建一个training_with_curated_multi_image_classification_datasets.ipynb笔记本的副本,这是你在使用精选视觉数据集训练多图像分类模型食谱中使用过的。将新副本命名为:training_with_curated_multi_image_classification_datasets_variations.ipynb

  2. 运行笔记本,直到并包括模型定义单元:

    learn = cnn_learner(dls, resnet18)
    
  3. 在此单元后立即添加以下单元并运行它:

    learn.summary()
    

    此单元的输出列出了模型的结构。请注意输出末尾的可训练和非可训练参数的总结,如下图所示:

    图 6.69 – 微调前总结输出末尾的可训练参数描述

    图 6.69 – 微调前总结输出末尾的可训练参数描述

  4. 更新模型训练单元,将训练次数设置为 20 个周期,然后运行它:

    learn.fine_tune(20)
    

    此单元的输出,如下图所示,显示了训练损失和验证损失在各个周期中的变化情况。注意训练损失在第 20 个周期之前稳步下降,而验证损失在第 10 个周期之后下降,然后开始波动。在这种情况下,训练损失不断下降,但验证损失停止下降,这表明了什么问题?

    图 6.70 – 20 个周期训练运行的结果

    图 6.70 – 20 个周期训练运行的结果

  5. 上一步显示训练更多周期并不会改善模型性能。你可以通过运行笔记本的其余部分并比较使用 20 个周期训练的模型如何预测所选测试图像中的物体来验证这一点。

  6. 在模型训练单元后立即添加以下内容并运行它:

    learn.summary()
    

    此单元的输出列出了模型的结构。请注意输出末尾的可训练和非可训练参数的总结,如下图所示。将此输出与微调模型前summary()的输出进行比较,如步骤 3所示。输出中summary()的差异变化有什么解释?

    图 6.71 – 微调后总结输出末尾的可训练参数描述

    图 6.71 – 微调后总结输出末尾的可训练参数描述

  7. 在之前食谱的工作原理...部分中,我们查看了PASCAL_2007的训练集大小,并提出其较小的大小可能是模型性能不理想的原因之一。另一个潜在原因是我们在定义DataBlock对象时调整图像大小的方式,如下单元所示:

    db = DataBlock(blocks=(ImageBlock, MultiCategoryBlock),
                     get_items = get_items, 
                     splitter=RandomSplitter(),
                     get_y=[get_lbl],
                     item_tfms = RandomResizedCrop(168,\
    min_scale=0.3),
                     n_inp=1)
    

    用于调整图像大小的转换是RandomResizedCrop(168, min_scale=0.3)。更新DataBlock定义单元格,尝试不同的图像转换。首先,更新RandomResizedCrop函数调用,尝试不同的图像大小和min_scale值。然后再次训练并在测试集的样本上测试模型,查看不同图像大小和min_scale值是否会改变性能。

  8. 尝试使用Resize而不是RandomResizedCrop作为转换函数(如下面的单元格所示),然后重新训练模型。查看在训练集的样本图像上使用训练好的模型时,是否能获得更好的结果:

    db = DataBlock(blocks=(ImageBlock, MultiCategoryBlock),
                     get_items = get_items, 
                     splitter=RandomSplitter(),
                     get_y=[get_lbl],
                     item_tfms = Resize(168),
                     n_inp=1)
    

恭喜你!你已经完成了在图像数据集上训练 fastai 模型的复习。

第七章:第七章:部署与模型维护

到目前为止,在本书中你已经训练了多种 fastai 模型,包括使用表格数据集训练的模型、使用文本数据集训练的模型、推荐系统以及使用图像数据训练的模型。所有这些模型都已经在 Jupyter Notebook 环境中进行了演示。Jupyter Notebook 很适合用于训练模型并使用几个测试样本进行测试,但如果要真正让模型有用怎么办?如何使你的模型可以供其他人或应用程序使用,以实际解决问题?

将你的深度学习模型提供给其他人或应用程序的过程称为 部署。在本章中,我们将通过一些步骤,展示如何部署你的 fastai 模型。深度学习模型的工业级生产部署超出了本书的范围。在本章中,你将学习如何创建简单、独立的部署,并能够从你自己的本地系统提供服务。

本章将涉及以下步骤:

  • 在本地系统上设置 fastai

  • 部署一个使用表格数据集训练的 fastai 模型

  • 部署一个使用图像数据集训练的 fastai 模型

  • 维护你的 fastai 模型

  • 测试你的知识

技术要求

在本章中,你将在本地系统上运行部署,这要求你在本地系统上安装 fastai。要在本地运行 fastai,推荐使用 Windows 或 Linux 系统,并安装 Python。虽然 fastai 也可以在 macOS 上安装,但如果你使用 Windows 或 Linux 系统进行本地安装,将能节省许多麻烦。

确保你已经克隆了本书的 GitHub 仓库 github.com/PacktPublishing/Deep-Learning-with-fastai-Cookbook,并可以访问 ch7 文件夹。该文件夹包含本章中描述的代码示例。

在本地系统上设置 fastai

在能够简单部署一个 fastai 深度学习模型的第一步是将你的本地系统设置为安装 PyTorch 和 fastai。你需要这么做,因为你将在本地系统上运行代码,调用你在本书中之前训练的模型。为了在本地系统上运行模型并进行预测,你需要安装 fastai 框架。在本节中,你将看到如何在本地系统上设置 fastai,以及如何验证你的安装。

准备工作

确保你在本地系统上安装了 Python(至少是 3.7)。

要检查 Python 的版本,可以在命令行中输入以下命令:

python –version

输出将显示你本地系统上安装的 Python 版本,如下所示:

图 7.1 – Python 版本

图 7.1 – Python 版本

确保您已经将书的存储库克隆到您的本地系统上:github.com/PacktPublishing/Deep-Learning-with-fastai-Cookbook

如何做…

要在本地系统上设置 fastai,您需要设置 PyTorch(fastai 运行的深度学习框架),然后是 fastai。要做到这一点,请按照以下步骤操作:

  1. 通过在本地系统的终端或命令窗口中运行以下命令,在您的本地系统上安装 PyTorch。您可以在此处找到有关在本地系统上安装 PyTorch 的完整详细信息:pytorch.org/get-started/locally/

    pip3 install torch==1.8.1+cpu torchvision==0.9.1+cpu torchaudio===0.8.1 -f https://download.pytorch.org/whl/torch_stable.html
    
  2. 根据您的操作系统和典型的 Python 安装方法,按照此处的说明在本地系统上安装 fastai:docs.fast.ai/

  3. 安装完 PyTorch 和 fastai 后,通过在本地 repo 的ch7目录中打开validate_local_setup.ipynb笔记本并运行以下单元格来验证您的安装:

    import fastai
    fastai.__version__
    

恭喜!你已成功在本地系统上设置了 fastai。

它是如何工作的…

您可能会问为什么有必要在本地系统上设置 fastai 来演示如何部署 fastai 模型。虽然可以在不使用本地系统的情况下部署 fastai 模型,但在本地安装 fastai 具有几个优点:

  1. 您可以完全控制整个环境。通过在本地安装 fastai,您可以控制整个堆栈,从 pandas 的层面到用于部署的网页的详细信息。

  2. 通过在本地部署 fastai 模型,您将避免可能会限制您完全理解 fastai 模型在部署时如何工作的快捷方式。您将在本章中看到的部署可能很简单,但它们是完整的。通过通过没有留下任何黑盒子的配方来工作,您将深入了解 fastai 模型在部署时真正发生的事情。

  3. 如果你认真对待 fastai 的使用,拥有本地安装环境会非常方便。在第一章**,快速入门 fastai中,我曾指定你需要一个云环境,像是 Gradient 或 Colab,以便进行本书中的实践部分。大多数 fastai 应用需要 GPU 才能高效训练。要在现有本地系统上设置 GPU 并不简单,除非你完全致力于通过全职工作在深度学习应用中定期使用 GPU,否则购买一个预配置有 GPU 的系统并没有太大意义。因此,使用启用 GPU 的云环境是最好的起点。然而,即便你不会在本地系统上进行模型训练,拥有一个可以运行 fastai 环境的本地系统也是非常有用的。例如,在编写本书的过程中,曾经有几次遇到 Gradient 环境的问题,而我大部分的开发工作都是在该环境中进行的。由于我本地已安装 fastai,当 Gradient 无法使用时,我依然可以在本地系统上继续编写与模型无关的代码,取得进展。

  4. 如果你之前没有接触过 web 应用开发,本章中的简短体验将对你有所帮助。根据我的经验,许多数据科学家对于 web 应用的工作原理一无所知,而我们大多数的工作最终都会在某种形式下通过 web 框架呈现,因此,理解 web 应用的基本工作原理对我们来说是非常重要的。通过结合 Python 的 Flask 库与基础的 HTML 和 JavaScript,我们将创建一个非常简单但完整的 web 应用,展示一些基本的 web 应用原理。如果你以前没有接触过这些原理,学会这些知识会对你非常有用。

我希望这些背景信息能够帮助你理解,在本地系统上拥有一个正常工作的 fastai 环境是多么有价值。现在你已经完成了 fastai 环境的设置,接下来的章节中,你将学会如何在本地系统上部署模型。

部署一个在表格数据集上训练的 fastai 模型

回到第三章**,训练表格数据的模型中的保存已训练的表格模型部分,你曾经练习过一个已保存的 fastai 模型。回想一下你在该部分中所经历的步骤。

首先,你按照以下方式加载了已保存的模型:

learn = load_learner('/storage/data/adult_sample/adult_sample_model.pkl')

然后你拿取了一个测试样本,并从模型中生成了该测试样本的预测:

test_sample = df_test.iloc[0]
learn.predict(test_sample)

如下截图所示,预测的输出包括了输入样本的值、预测结果以及每个结果的概率:

图 7.2 – 运行已保存的 adult_sample_model 模型进行预测的输出

图 7.2 – 运行已保存的 adult_sample_model 模型进行预测的输出

在本食谱中描述的模型网页部署过程中,你将按照与我们刚刚回顾的第三章《使用表格数据训练模型》食谱中完全相同的步骤(如下所列)进行操作:

  1. 加载已保存的训练模型。

  2. 将模型应用到输入样本上。

  3. 获取模型的预测结果。

第三章《使用表格数据训练模型》中所有操作都在 Jupyter notebook 环境下进行不同,在这个示例中,你将通过一个简单的网页应用程序完成这些步骤。你将能够以非常自然的方式输入新的样本并获得预测结果,预测结果会以清晰的英文语句呈现,而不是张量的形式。更棒的是,你将能够与他人分享你的网页应用程序,让他们也能使用你的模型并查看其做出的预测。简而言之,通过部署你的模型,你将其从一个只能在程序中访问的抽象编码工件转变为一个普通人也能实际使用的软件工具。

本节描述的部署过程包含一个作为 Flask 模块实现的网络服务器。Flask 是一个 Python 库,它让你可以在熟悉的 Python 环境中提供网页应用程序。在本示例中,你将启动 Flask 模块,并使用它所提供的网页来调用模型。

准备工作

确保你已经按照在本地系统上设置 fastai这一食谱中的步骤安装了 fastai,并确认你可以访问 ch7 目录下 deploy_tabular 目录中的文件。

如何操作…

要在你的系统上部署一个训练好的表格数据集模型,你将启动 Flask 服务器,并通过相关的网页来验证你是否能根据给定的输入评分参数从模型中获取预测。完成以下步骤来操作:

  1. 在本地系统的命令窗口/终端中,将 ch7 目录下的 deploy_tabular 设置为当前目录。

  2. 在命令行/终端中输入以下命令来启动 Flask 服务器:

    localhost:5000, as shown in the following screenshot:![Figure 7.3 – Output when the Flask server starts    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_7_3.jpg)Figure 7.3 – Output when the Flask server starts
    
  3. 打开浏览器窗口,并在地址栏输入以下内容:

    home.html web page will be loaded in the browser, as shown in *Figure 7.4*:![Figure 7.4 – home.html being server by the Flask server    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_7_4.jpg)Figure 7.4 – home.html being server by the Flask server
    
  4. 现在,选择 home.html,在此案例中,选择字段的默认值:

图 7.5 – 在 home.html 中使用默认设置生成的查询字符串

图 7.5 – 在 home.html 中使用默认设置生成的查询字符串

几秒钟后,show-prediction.html 网页将显示模型对 home.html 中输入的值进行预测的结果,如图 7.6所示:

图 7.6 – 模型在 show-prediction.html 中显示的预测结果

图 7.6 – 模型在 show-prediction.html 中显示的预测结果

恭喜!你已成功设置了 Flask 服务器,并在简单网页部署的背景下练习了 fastai 模型的网页部署。

它是如何工作的…

当你运行这个配方时,幕后发生了很多事情。在本节中,我们将首先概述网页部署的流程,然后深入研究构成该部署的关键代码部分。

快速介绍 fastai 表格模型的网页部署工作原理

本配方中描述的网页部署与本书前面看到的配方有所不同。与其他配方(它们涉及以 Jupyter 笔记本形式呈现的单个代码文件)不同,网页部署涉及多个文件中分布的代码,如 图 7.7 所示:

图 7.7 – 使用 Flask 部署 fastai 模型的网页概述

图 7.7 – 使用 Flask 部署 fastai 模型的网页概述

以下是 图 7.7 中数字所标示的关键项:

  1. home.html – 这是用户指定每个特征的网页,用于训练模型时使用的特征。home.html 包含一组 JavaScript 函数,这些函数设置每个控件中的可用值,打包用户的输入,并调用 show-prediction.html,将评分参数作为参数传递。

  2. Flask web_flask_deploy.py 模块 – 一个 Python 模块,使用 Flask 库来服务构成网页部署的网页。该模块包括 home.htmlshow-prediction.html,它们完成了网页部署的大部分工作。show-prediction.html 的视图函数解析从 home.html 发送来的评分参数,将评分参数值组装成 DataFrame,使用包含评分参数的 DataFrame 调用训练好的模型进行预测,生成模型预测的字符串,最后触发 show-prediction.html 显示预测字符串。

  3. fastai adult_sample_model.pkl 模型 – 这是你在 第三章 《使用表格数据训练模型》 中的 保存训练好的表格模型 配方中训练并保存的模型。web_flask_deploy.py Flask 模块中的 show-prediction.html 视图函数加载此模型,并使用它对在 home.html 中输入的评分参数进行预测。

  4. show-prediction.html – 这个网页显示模型根据在 home.html 中输入的评分参数所做的预测。用户阅读预测结果后,可以选择返回 home.html 输入另一组评分参数。

这是关于网页部署工作原理的高层次总结。接下来,我们将查看一些构成该部署的关键代码部分。

深入探索网页部署背后的代码

现在你已经了解了幕后发生的高层次过程,让我们深入探讨两段对整体 Web 部署特别重要的代码。我们将逐步解析部署中组成部分的主要代码,包括 Flask 服务器模块中的 Python 代码以及 HTML 文件中的 JavaScript 函数。

当你启动 Flask 服务器时,如食谱中的 步骤 2 所示,训练好的模型将被加载到 Python 模块中,以下是来自 Flask 服务器代码的片段:

path = Path(os.getcwd())
full_path = os.path.join(path,'adult_sample_model.pkl')
learner = load_learner(full_path)

这是该片段的关键部分:

  • path = Path(os.getcwd()) – 设置 path 为启动 Flask 服务器的目录。代码假设模型文件位于相同的目录中。

  • full_path = os.path.join(path,'adult_sample_model.pkl') – 定义了模型的完整路径,包括文件名。

  • learner = load_learner(full_path) – 将模型加载到 learner 中。

当你在浏览器中访问 localhost:5000 时,将显示 home.html 页面。这个过程是如何发生的呢?在 web_flask_deploy.py Flask 模块中,home.html 控制了当 Flask 服务器运行时,你访问 localhost:5000 时发生的事情,如以下代码片段所示:

@app.route('/')
def home():   
    title_text = "fastai deployment"
    title = {'titlename':title_text}
    return render_template('home.html',title=title)  

这是此视图函数的关键部分:

  • @app.route('/') – 指定当你访问 localhost:5000 地址时应用此视图函数。

  • return render_template('home.html',title=title) – 指定当你访问 localhost:5000 时显示 home.html

home.html 被加载时,操作从 Flask 服务器模块中的 Python 转移到 home.html 中的 HTML 和 JavaScript 的结合体。首先,调用 load_selections() 函数将值加载到网页上的控件中,如以下 HTML 语句所示:

<body onload="load_selections()">

load_selections() 函数通过指定有效值的列表填充页面上的选择控件(下拉列表),例如以下 relationship 控件的内容:

var relationship_list = [" Wife" ," Not-in-family" ," Unmarried" ," Husband" ," Own-child" ," Other-relative" ];

load_selections() 函数还包括 for 循环,这些循环将有效值列表设置到选择控件中,例如以下 for 循环填充 relationship 控件:

for(var i = 0; i < relationship_list.length; i++) {
      var opt = relationship_list[i];
      select_relationship.innerHTML += "<option value=\"" + opt + "\">" + opt + "</option>";

对于输入数值的控件,load_selections() 函数设置页面加载时显示的默认值。例如,以下 load_selections() 函数中的语句为 age 字段设置了默认值:

document.getElementById("age").defaultValue = 40;

一旦值被加载到控件中并且页面显示,用户可以在控件中选择与默认值不同的评分参数值。

在用户选择了评分参数的值后,用户可以选择按钮,调用 link_with_args() 函数:

<button>
<a onclick="link_with_args();" style="font-size : 20px; width: 100%; height: 100px;">Get prediction</a>
</button>

link_with_args() 函数调用 getOption() 函数,加载用户在 home.html 中选择的控件值,并用这些值构建查询字符串,如下所示,这是 getOption() 中的代码片段:

prefix = "/show-prediction/?"
window.output = prefix.concat("workclass=",workclass_string,"&age=",age_value,"&fnlwgt=",fnlwgt_value,"&education=",education_string,"&education-num=",education_num_value,"&marital-status=",marital_status_string,"&occupation=",occupation_string,"&relationship=",relationship_string,"&race=",race_string,"&sex=",sex_string,"&capital-gain=",capital_gain_value,"&capital-loss=",capital_loss_value,"&hours-per-week=",hours_per_week_value,"&native-country=",native_country_string);
document.querySelector('.output').textContent = window.output;

以下是该代码片段的关键部分:

  • prefix = "/show-prediction/?" – 指定当链接被触发时,Flask 模块中将调用哪个视图函数。

  • window.output – 指定查询字符串中包含的参数集。这个字符串由一系列键值对组成,每个值等于在 home.html 中对应的控件。

  • document.querySelector('.output').textContent = window.output; – 指定查询字符串将在浏览器窗口中显示。

你可能记得在这个过程中的查询字符串。在食谱的步骤 4中,当你选择 home.html 时,查询字符串会在页面底部短暂显示,随后加载 show-prediction.html

在调用 getOption() 后,link_with_args() 函数通过以下语句触发对 show-prediction.html 的引用:

window.location.href = window.output;

通过这个语句,操作从 HTML 和 JavaScript 的世界切换回 Python,并在 Flask 服务器中调用 show-prediction.html 的视图函数。以下是该视图函数的开始部分,其中在 home.html 中输入并通过查询字符串传递的评分参数值被加载到 score_df DataFrame 中:

@app.route('/show-prediction/')
def show_prediction():
     score_df = pd.DataFrame(columns=scoring_columns)
for col in scoring_columns:
        print("value for "+col+" is: "+str(request.args.get(col)))    
        score_df.at[0,col] = request.args.get(col)

以下是该代码片段的关键部分:

  • @app.route('/show-prediction/') – 指定此视图函数适用于 show-prediction.html 网页。

  • score_df = pd.DataFrame(columns=scoring_columns) – 创建一个空的 DataFrame 来存储评分参数。

  • score_df.at[0,col] = request.args.get(col) – 这个语句会针对 scoring_columns 列表中的每一列运行。它将查询字符串中由 getOption() JavaScript 函数构建并作为引用传递到 show-prediction.html 的值复制到 score_df DataFrame 第一行的对应列。这就是用户在 home.html 中输入的评分参数值如何传入 Python Flask 服务器模块的方式。

现在,评分参数已被加载到 score_df DataFrame 的第一行中,我们可以在 DataFrame 的第一行上调用模型,正如以下来自 show-prediction.html 视图函数的代码片段所示:

pred_class,pred_idx,outputs = learner.predict(score_df.iloc[0])
if outputs[0] >= outputs[1]:
        predict_string = "Prediction is: individual has income less than 50k"
    else:
        predict_string = "Prediction is: individual has income greater than 50k"
    prediction = {'prediction_key':predict_string}
    return(render_template('show-prediction.html',prediction=prediction))

以下是该代码片段的关键部分:

  • pred_class,pred_idx,outputs = learner.predict(score_df.iloc[0]) – 使用 score_df DataFrame 的第一行作为输入调用模型。此调用有三个输出:

    a) pred_class 列出了输入到模型中的评分参数。对于类别列,原始的评分参数值被替换为类别标识符。例如,native-country 列中的 United States 被替换为 40.0。这些转换与训练数据时所做的转换完全相同,正如你在 第三章**《使用表格数据训练模型》 中所做的那样。由于 fastai 管理这些转换的方式,不像 Keras,你不需要担心在部署模型时维护管道对象并应用它——fastai 会自动处理这一切。这是 fastai 的一个巨大优势。

    b) pred_idx – 预测的索引。对于此模型,预测值将为 0(表示个人收入低于 50,000)或 1(表示个人收入高于 50,000)。

    c) outputs – 显示每个预测值的概率。

    图 7.8 显示了预测输出的示例,以及它如何对应于 pred_classpred_idxoutputs 变量:

图 7.8 – 模型预测输出示例

图 7.8 – 模型预测输出示例

  • return(render_template('show-prediction.html',prediction=prediction)) – 指定以该视图函数中设置的参数值显示 show-prediction.html

执行此操作后,页面返回到 HTML,show-prediction.html 被加载到浏览器中。以下代码片段显示了展示预测文本的 HTML:

<div class="home">
  <h1 style="color: green"> 
    Here is the prediction for the individual's income:
  </h1> 
  <h1 style="color: green"> 
    {{ prediction.prediction_key }}
  </h1>

{{ prediction.prediction_key }} 值对应于 Flask 服务器中 show-prediction 的视图函数中设置的 predict_string 值。结果是,模型对评分参数做出的预测会被显示出来,如 图 7.9 所示:

图 7.9 – 部署模型的最终结果 – 对评分参数的预测

图 7.9 – 部署模型的最终结果 – 对评分参数的预测

现在你已经看到了构成整个 fastai 模型 Web 部署流程的所有主要代码项。该流程包括以下几个步骤:

  1. 流程从启动 Flask 服务器开始。一旦启动了 Flask 服务器,它就准备好在 localhost:5000 提供 home.html

  2. 当你在浏览器中访问 localhost:5000 时,Flask 服务器会运行 home.html 的视图函数,并在浏览器中显示 home.html

  3. 然后流程转到 home.html 中的 HTML/JavaScript,用户在此选择评分参数并点击 获取预测 按钮。

  4. 然后流程返回到 Flask 服务器,运行 show-prediction.html 的视图函数,从模型中获取评分参数的预测,并在浏览器中展示 show-prediction.html

  5. 最后,流程回到show-prediction.html,在该页面上展示模型的预测结果。

  6. 此时,用户可以选择show-prediction.html,使用不同的评分参数从步骤 2重新开始整个过程。

还有更多……

本食谱中的网络部署示例仅仅触及了 Flask 的表面,它只涵盖了现代 HTML 和 JavaScript 的基本应用。本书的范围无法深入探讨如何使用 Python 开发 Web 应用,但如果你有兴趣了解更多,可以参考以下资源:

部署一个基于图像数据集训练的 fastai 模型

使用 fastai 部署基于表格数据集训练的模型这一食谱中,我们讲解了如何部署一个基于表格数据集训练的模型。我们部署了一个根据一组被称为评分参数的特征(包括教育水平、工作类别和每周工作小时数)来预测个人是否会有超过 50,000 收入的模型。为了进行这个部署,我们需要一种方式让用户选择评分参数的值,并展示由训练好的 fastai 模型基于这些评分参数所做的预测。

在本教程中,我们将部署在第六章**,训练具有视觉数据的模型中的 使用独立视觉数据集训练分类模型 处训练的图像分类模型。此模型可以预测图像中展示的水果或蔬菜。与表格数据集模型的部署不同,部署图像数据集模型时,我们需要能够指定要进行预测的图像文件。

注意

为了简化操作,此部署使用与我们在部署基于表格数据集训练的 fastai 模型教程中相同名称的网页(home.htmlshow-prediction.html)。不过,这些网页是为图像模型部署定制的。

准备工作

确保你已经按照在本地系统上设置 fastai教程中的步骤,成功安装了 fastai。确认你可以访问 ch7 目录中的 deploy_image 目录下的文件。

如何操作……

要在本地系统上演练图像分类模型的部署,首先启动 Flask 服务器,打开浏览器中部署的 home.html 页面,选择一个图像文件进行预测,然后验证在该部署的 show-prediction.html 页面中是否显示了图像的预测结果。

按照以下步骤进行操作,以演练部署在图像数据集上训练的 fastai 模型:

  1. 在本地系统的命令窗口/终端中,将 ch7 目录中的 deploy_image 目录设置为当前目录。

  2. 在命令行/终端输入以下命令以启动 Flask 服务器:

    localhost:5000, as shown in *Figure 7.10*:![Figure 7.10 – Output when the Flask server starts    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_7_10.jpg)Figure 7.10 – Output when the Flask server starts
    
  3. 打开浏览器窗口,在地址栏输入以下内容:

    home.html will be loaded in the browser, as shown in *Figure 7.11*:![Figure 7.11 – home.html for the image model deployment being served by the Flask server    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_7_11.jpg)Figure 7.11 – home.html for the image model deployment being served by the Flask server
    
  4. 现在选择 deploy_images 目录中的 test_images 子目录。选择柠檬图像文件 5_100.jpg,并关闭文件对话框,例如,在 Windows 中选择 打开

  5. 当文件对话框关闭时,你选择的文件名会显示在 home.html 中的 选择文件 按钮旁边,如图 7.12所示:图 7.12 – 你选择的文件名显示在 home.html 页面中

    图 7.12 – 你选择的文件名显示在 home.html 页面中

  6. 现在选择show-prediction.html 页面,该页面显示模型对你在 home.html 中选择的图像的预测结果,如图 7.13所示:

图 7.13 – 模型对图像内容的预测,显示在 show-prediction.html 页面中

图 7.13 – 模型对图像内容的预测,显示在 show-prediction.html 页面中

恭喜!你已成功设置了 Flask 服务器并演练了 fastai 模型的 Web 部署,该模型可以预测图像中展示的物体。

工作原理……

现在你已经完成了 fastai 图像分类模型的 Web 部署,让我们来看一下幕后发生了什么。我们将从部署的概述开始,然后深入探讨图像分类模型和表格数据集模型部署之间的代码差异,正如在 《部署一个基于表格数据集训练的 fastai 模型》 配方中所描述的那样。

fastai 图像分类模型 Web 部署的工作概述

让我们回顾一下如 图 7.14 所示的端到端部署流程:

图 7.14 – 使用 Flask 部署 fastai 图像分类模型的 Web 概述

图 7.14 – 使用 Flask 部署 fastai 图像分类模型的 Web 概述

以下是 图 7.14 中数字高亮的关键项目:

  1. home.html——这是用户指定要让模型对其进行预测的图像文件的网页。用于图像分类模型部署的 home.html 版本包含了显示文件选择对话框、打包选中文件名称并调用 show-prediction.html 的 HTML 和 JavaScript 函数,并将选定的图像文件名作为参数传递。

  2. Flask web_flask_deploy_image_model.py 模块——这个 Python 模块使用 Flask 库来提供构成 Web 部署的网页。该模块包括 home.htmlshow-prediction.html 的视图函数。show-prediction.html 的视图函数接收从 home.html 中选择的图像文件名称,使用该文件名调用训练好的模型进行预测,生成模型预测的字符串,并最终触发 show-prediction.html 显示预测字符串。

  3. fastai fruits_360may3.pkl 图像分类模型——这是你在 《使用独立视觉数据集训练分类模型》 配方中训练并保存的模型,位于 第六章**,《使用视觉数据训练模型》一章中。web_flask_deploy_image_model.py Flask 模块中的 show-prediction.html 视图函数加载这个模型,然后使用它对 home.html 中选择的图像文件进行预测。

  4. show-prediction.html——这个网页展示了模型对 home.html 中选择的图像文件所做的预测。在这个页面,用户可以选择 home.html 来选择另一张图像文件进行预测。

这就是图像分类模型 Web 部署工作的高级总结。

深入分析图像分类模型 Web 部署背后的代码

现在我们已经回顾了图像分类模型部署的整体流程,接下来让我们来看一下与我们在《部署基于 tabular 数据集训练的 fastai 模型》配方中涉及的 tabular 模型部署相比,图像分类模型部署的一些关键区别。以下是主要的区别:

  • home.html中的 HTML – 用于部署 tabular 数据集模型的home.html版本需要大量控件,以便用户能够指定所有必需的评分参数。用户需要能够为训练模型使用的所有特征指定值。对于图像分类模型的部署,只有一个特征——图像文件——因此我们只需要一个用于选择文件的控件。以下是文件选择控件的 HTML:

      <label for="image_field">Please choose an image:</label>
      <input type="file"
           id="image_field" name="image_field"
           accept="image/png, image/jpeg">  
    

    以下是该 HTML 片段中的关键内容:

    a) input type="file" – 指定该控件用于从本地文件系统输入文件。

    b) accept="image/png, image/jpeg" – 指定从此控件打开的文件对话框只允许选择具有image/pngimage/jpeg内容类型的图像文件。

  • home.html中的 JavaScript – 用于部署 tabular 数据集模型的home.html版本包含三个 JavaScript 函数:

    a) getOption(),用于从控件中获取值。

    b) link_with_args(),用于调用getOption()并将查询字符串发送到视图函数show-prediction.html

    c) load_selections(),用于初始化控件。

    用于图像分类模型部署的home.html版本不需要load_selections()(因为没有需要初始化的控件),其版本的link_with_args()与 tabular 模型部署版本相同。剩下的是getOption()函数,它与 tabular 模型部署中的版本有显著区别。以下是图像分类部署版本的getOption()

    function getOption() { 
          var file_value = [];
          const input = document.querySelector('input');
          const curFiles = input.files;
          if(curFiles.length === 0) {
            console.log("file list empty");
    
          } else {
          for(const file of curFiles) {
            file_value.push(file.name);
            }
          }
          prefix = "/show-prediction/?"
          window.output = prefix.concat("file_name=",file_value[0])
        }
    

    以下是getOption()定义中的关键内容:

    a) const input = document.querySelector('input'); – 将input与文件选择器关联起来。

    b) const curFiles = input.files; – 将与文件选择器关联的文件列表赋值给curFiles

    c) for(const file of curFiles) { file_value.push(file.name);} – 遍历与文件选择器关联的文件列表,并将每个文件名添加到file_value列表中。

    d) window.output = prefix.concat("file_name=",file_value[0]) – 使用file_value文件名列表中的第一个元素构建查询字符串。由于我们每次只对一个文件进行预测,因此查询字符串只需要一个文件名。最终生成的查询字符串类似于:/show-prediction/?file_name=5_100.jpg

  • Flask 服务器中的 show-prediction.html 视图函数 – 以下代码片段展示了图像分类 Web 部署的视图函数:

    @app.route('/show-prediction/')
    def show_prediction():
        image_file_name = request.args.get("file_name")
        full_path = os.path.join(path,image_directory,image_file_name)
        img = PILImage.create(full_path)
        pred_class, ti1, ti2 = learner.predict(img)
        predict_string = "Predicted object is: "+pred_class
        prediction = {'prediction_key':predict_string}
        return(render_template('show-prediction.html',prediction=prediction))
    

    以下是定义此视图函数时的关键项:

    a) image_file_name = request.args.get("file_name") – 将 image_file_name 的值设置为查询字符串中的文件名。

    b) full_path = os.path.join(path,image_directory,image_file_name) – 将 full_path 的值设置为在 home.html 中选择的图像文件的完整文件名。假设该文件是从运行 Flask 服务器的目录中的 test_images 子目录中选择的。

    c) img = PILImage.create(full_path) – 为在 home.html 中选择的图像文件创建一个名为 img 的图像对象。

    d) pred_class, ti1, ti2 = learner.predict(img) – 从图像分类模型中获取预测结果,预测的对象是 imgpred_class 包含模型对图像文件预测的类别(如 苹果)。

    e) return(render_template('show-prediction.html',prediction=prediction)) – 指定显示 show-prediction.html 并在此视图函数中设置参数值。

现在你已经看到了在表格数据集模型部署与图像分类模型部署之间的所有主要代码差异。

还有更多内容……

在本章中,你已经看到了两个使用基于 Flask 的 Web 应用程序部署 fastai 模型的示例。这并不是你可以采用的唯一部署模型的方法。其他方法包括通过 REST API 端点部署模型(以便其他应用程序可以直接调用模型),或将模型及其依赖项封装到其他应用程序中。可以将模型和依赖项(例如所需的 Python 库)打包到 Docker 容器中,然后通过像 Kubernetes 这样的编排系统将这些容器提供给其他应用程序。

与其停留在这些一般的部署概念上,可能更有用的是回顾一些具体的快速部署 fastai 模型的方法。以下是部署 fastai 模型的一些方法示例:

这个列表并不详尽,但它展示了可供 fastai 模型部署的各种选项。

维护你的 fastai 模型

部署一个模型并不是故事的结束。一旦你部署了一个模型,你需要维护该部署,以确保它与模型训练所使用的当前数据特征匹配。如何在生产环境中维护深度学习模型的详细描述超出了本书的范围,但值得简要提及如何在本章所描述的简单模型部署环境下维护模型。在本教程中,我们将讨论你可以采取哪些措施来维护在 部署基于表格数据集训练的 fastai 模型 中部署的表格模型。

准备工作

确保你已经按照 在本地系统上设置 fastai 这个教程的步骤完成安装,确保 fastai 已经安装在你的本地系统中。同时,确保你已经启动了用于表格模型部署的 Flask 服务器,可以通过遵循 部署基于表格数据集训练的 fastai 模型 这个教程中的 步骤 1, 2 和 3 来完成。

在这个教程中,你将对用于训练表格模型的数据进行一些基本分析,这个模型已在 部署基于表格数据集训练的 fastai 模型 这个教程中进行了部署。为了进行此分析,请确认你可以使用你选择的电子表格应用(如 Excel 或 Google Sheets)打开 adult.csv 文件,这是 ADULT_SAMPLE 数据集中的训练数据文件。如果你尚未在本地系统中拥有 adult.csv,请按照以下步骤获取该文件,并确认你可以使用电子表格应用打开它:

  1. 在你的 Gradient 环境中,在终端窗口输入以下命令,将 adult.csv 复制到你的 temp 目录:

    cp /storage/data/adult_sample/adult.csv /notebooks/temp/adult.csv
    
  2. 在你的 Gradient 环境中的 JupyterLab 中,导航到你在前一步复制 adult.csvtemp 目录,右键点击 adult.csv 并选择 下载

  3. 使用你的电子表格应用程序打开在上一步中下载的本地adult.csv副本。图 7.15 显示了adult.csv在 Excel 中的前几行:

图 7.15 – adult.csv 在 Excel 中的前几行

图 7.15 – adult.csv 在 Excel 中的前几行

注意

你可能会问,为什么我建议使用电子表格来检查此配方中的数据,而不是使用 Python?我推荐在这里使用电子表格有几个原因。首先,甚至像 Jeremy Howard 这样的权威人物都表示 Excel 是一个很棒的数据科学工具,而我恰好认为他完全正确。它灵活、轻便,并且对于在小数据集上进行简单调查,比 Python 更快。其次,Excel 帮助我调试了表格模型部署中的问题。当我第一次测试部署时,我很难理解为什么部署的模型与在 Python 笔记本中调用的模型产生不同的预测。然而,一旦我在 Excel 中检查数据,问题就显而易见了:用于训练模型的数据中的所有类别值都以空格开头。而在部署中,用户可以选择的类别值并没有以空格开头,因此模型没有将它们识别为与训练时遇到的类别值相同。Excel 给了我一个快速的方式来检测问题的根本原因。

如何操作…

为了执行一些模型维护操作,请完成以下步骤:

  1. 首先,仔细观察ADULT_SAMPLE中类别值的表示方式。如果你还没有在电子表格应用程序中打开本地的adult.csv副本,现在就打开它。选择workclass列中的一个值。你是否注意到该值有任何不寻常的地方?查看其他一些类别列中的值:relationshipnative-country。你会看到每个类别列中的值都以空格开头。

  2. 请回想,在home.html中,用户在每个类别特征上可以选择的值是有限制的。打开表格模型部署中的home.html,查看workclass可以选择的值。图 7.16 显示了用户可以为workclass选择的值:图 7.16 – home.html 中可用的  值

    图 7.16 – home.html 中可用的 workclass

  3. 用户在home.html中可以选择的类别列的值是通过load_selections() JavaScript 函数中的一系列列表定义的。以下是load_selections() JavaScript 函数中为workclassrelationshipnative-country定义的列表:

    var workclass_list = [" Private" ," Self-emp-inc" ," Self-emp-not-inc" ," State-gov" ," Federal-gov" ," Local-gov" ];
    var relationship_list = [" Wife" ," Not-in-family" ," Unmarried" ," Husband" ," Own-child" ," Other-relative" ];
    var native_country_list = [" United-States"," Puerto-Rico"," Mexico"," Canada"," Taiwan"," Vietnam"," Philippines"];
    

    请注意,这些列表中的值每个都以一个空格开始,就像adult.csv中对应类别列的值一样。这些列表中的值用于构建查询字符串,查询字符串又被用作输入,以从模型中获取预测,作为show-prediction.html视图函数的输入。如果home.html中的列表值定义时没有前导空格,会发生什么情况?

  4. 假设模型的训练数据已经扩展,包括来自adult.csv的数据,这些数据在native-country列中包含United-Kingdom值。你需要做以下操作来更新部署以适应这个变化:

    a) 使用新版本的adult.csv重新训练模型,并使用learner.export() fastai API 将新的训练模型保存为pkl文件。为了本教程的目的,假设你将新的模型命名为adult_sample_model_new.pkl

    b) 将更新后的adult_sample_model_new.pkl模型文件复制到本地系统的deploy_tabular目录。

    c) 更新web_flask_deploy.py Flask 服务器模块中模型路径的定义,包含新的模型文件名:

    full_path = os.path.join(path, 'adult_sample_model_new.pkl')
    

    d) 更新home.html中的native_country_list,以包括新的值:

    var native_country_list = [" United-States"," Puerto-Rico"," Mexico"," Canada"," Taiwan"," Vietnam"," Philippines", "  United-Kingdom"  ];
    

    对于任何类别列的新值,你需要采取相同的步骤来更新部署:使用更新后的训练数据集重新训练模型,将更新后的训练模型复制到部署目录,更新 Flask 服务器以加载更新后的模型,并在home.html中更新有效的类别值列表。

  5. 在上一阶段,我们了解了如果数据集随着新的类别值扩展,我们需要做什么。如果数据集中添加了一个全新的列呢?就像步骤 4中描述的过程一样,你需要在包含新列的更新训练数据集上重新训练模型,将新模型复制到deploy_tabular目录,并更新web_flask_deploy.py以加载新模型。最后,你还需要更新home.html,以便用户能够输入新列的信息。你需要进行的更新取决于新列是否在每个home.html的情况下。

  6. 假设你需要更新部署,以处理一个名为years-in-job的新连续列——即记录个人在当前工作岗位上工作的年数。有效值为 0 到 45,默认值为 5。为了添加这个列,你需要对home.html进行几项更新。首先,你需要添加以下代码来定义这个新列的控制项:

    <p> 
      <label for="years-in-job">years in job (0 - 45):</label>
      <input type="number" id="years-in-job" name="years-in-job"  min="0" max="45">
      </p>
    

    接下来,你需要在load_selection() JavaScript 函数中添加以下行,以设置默认值:

    document.getElementById("years-in-job").defaultValue = 5;
    

    接下来,你需要在getOption() JavaScript 函数中添加以下行,以设置将包含在查询字符串中的这个列的值:

    years_in_job_value = document.getElementById("years-in-job ").value;
    

    最后,你需要在用于定义window.output的查询字符串末尾添加以下内容:

    ,"&years-in-job=",years_in_job_value
    
  7. 假设你需要更新部署,处理一个新的分类列 work-location,它指定个人当前工作的地点。该列的有效值包括 remote(远程)、on-site(现场)和 mixed(混合)。为了让 home.html 适应这个新列,首先通过添加以下代码来定义 work-location 列的控制:

      <p>
        Select work location:
        <select id="work-location">
        </select>
      </p>
    

    接下来,向 load_selection() JavaScript 函数添加以下几行代码,以设置新列控制的值。请注意,我们假设与其他分类列一样,work-location 中的值将以空格开头,因此 work_location_list 中的所有值都以空格开始:

    var select_work_location = document.getElementById("work-location");
    var work_location_list = [" remote"," on-site"," mixed"];
    for(var i = 0; i < work_location_list.length; i++) {
          var opt = work_location_list[i];
          select_work_location.innerHTML += "<option value=\"" + opt + "\">" + opt + "</option>";
        }
    

    接下来,向getOption() JavaScript 函数添加以下几行代码,以设置查询字符串中包含该列的值:

    selectElementworklocation = \
    document.querySelector('#work-location'); 
    work_location_string =\
    selectElementworklocation.options[selectElementworklocation.selectedIndex].value
    

    最后,向用于定义 window.output 的查询字符串的末尾添加以下内容:

    ,"&work-location=",work_location_string
    

恭喜!你已经完成了维护模型部署的一些必要操作,确保它能够适应训练数据集的变化。

它是如何工作的……

在这个食谱中,我们回顾了如何维持在表格数据上训练的模型的网络部署。我们了解了为适应训练数据集变化而调整部署的步骤。我们讨论的那些数据集变化包括现有分类列中的新值、新的连续列和新的分类列。

在工业级生产部署中,数据集的架构,即构成数据集的所有列的特征,将保存在 HTML 文件之外。例如,我们可能会将架构保存在一个单独的配置文件中,里面包含有关数据集列的信息。这样一来,home.html 中的控制和有效值就不是硬编码的,而是根据配置文件的内容动态生成。

使用这种动态设置方式,当数据集添加新列或列的有效值发生变化时,我们只需更新配置文件中的数据集架构定义,home.html 将会自动更新。为了让网络部署尽可能简单,我们将控制和有效值直接编写在 home.html 中,而不是动态生成它们。这使得 基于表格数据训练 fastai 模型的部署 食谱更易于跟随,但也意味着在 home.html 中有多个地方需要更新,以维持数据集发生变化时的部署。

还有更多内容……

在这个食谱中,我们讨论了如何处理数据集架构的变更,但我们没有讨论如何处理数据集分布的变化,或者如何监控模型以确保它随着时间的推移保持其性能。这两个问题对于维持已部署的模型至关重要,但它们超出了本书的范围。

如果你有兴趣了解更多关于在生产环境中监控模型性能的内容,这篇文章提供了一个很好的概述:christophergs.com/machine%20learning/2020/03/14/how-to-monitor-machine-learning-models/

测试你的知识

现在你已经部署了两种 fastai 模型,并解决了与维护已部署模型相关的一些挑战,你可以尝试一些额外的部署变种,来锻炼你所学到的内容。

准备就绪

确保你已经按照在本地系统上设置 fastai教程中的步骤,在本地系统上安装了 fastai。同时,确保你已经按照部署在图像数据集上训练的 fastai 模型教程中的第 1 步、第 2 步第 3 步,启动了 Flask 服务器以进行图像分类模型的部署。

为了在图像分类模型部署上进行实验,复制deploy_image目录。为此,将包含deploy_image的目录设置为当前目录,并运行以下命令来复制该目录及其内容,命名为deploy_image_test

cp -r deploy_image deploy_image_test

如何做到……

你可以按照此教程中的步骤,扩展和增强你在部署在图像数据集上训练的 fastai 模型教程中遵循的模型部署,以允许用户在home.html中选择多个图像文件,并在show-prediction.html中显示所有图像的预测结果:

  1. deploy_image_test设置为当前目录。

  2. 为了允许用户选择多个文件并同时显示所有文件的预测结果,你需要更新home.html、Flask 服务器和show-prediction.html

  3. 首先更新home.html,使用户能够在文件对话框中选择多个文件。向文件对话框控件的定义中添加multiple属性,如下所示的 HTML 代码片段所示:

      <input type="file" multiple
           id="image_field" name="image_field"
           accept="image/png, image/jpeg">
    

    现在,用户将能够在文件对话框中选择多个文件。

  4. 接下来,更新home.html中的getOption() JavaScript 函数,构建一个文件名列表,将其添加到查询字符串中,并发送回 Flask 服务器。更新后的getOption()函数如下所示:

    function getOption() { 
         var file_value = [];
      var file_count = 0;
      const input = document.querySelector('input');
      var file_path = input.value;
      const curFiles = input.files;
      if(curFiles.length === 0) {
        console.log("file list empty");
       } else {
      for(const file of curFiles) {
        if (file_count == 0) {
          file_count = 1;
          file_list_prefix = "&file_name=";
          var file_list = file_list_prefix.concat(file.name);
        } else {
          file_list = file_list.concat("&file_name=",file.name);
        }
        file_value.push(file.name);
      }
      }
      prefix = "/show-prediction/?"
      window.output = prefix.concat("file_path=",file_path,file_list)
    }  
    

    以下是getOption()函数中的关键更新项:

    a) var file_list = file_list_prefix.concat(file.name); – 指定如果这是第一个文件,则初始化file_list字符串。

    b) file_list = file_list.concat("&file_name=",file.name); – 指定如果这不是第一个文件,将文件名添加到file_list字符串的末尾。

    c) window.output = prefix.concat("file_path=",file_path,file_list) – 指定查询字符串包括file_list字符串,其中包含用户选择的所有图像文件的文件名。

    你已经完成了home.html中所需的更新,以处理多个图像文件。

  5. 现在是时候更新 Flask 服务器了。首先,向 Flask 服务器中添加以下函数。稍后你将使用这个函数构建要发送到 show-prediction.html 的参数:

    def package_list(key_name,list_in):
        i = 0
        list_out = []
        for element in list_in:
            key_value = list_in[i].strip()
            list_out.append({key_name:key_value})
            i = i+1
        return(list_out)
    
  6. 接下来,更新 show-prediction.html 的视图函数。首先,你需要将你在 home.htmlgetOption() 函数中构建的文件名列表转为 Python 列表。以下语句将创建一个名为 image_file_name_list 的列表:

    image_file_name_list = request.args.getlist('file_name')
    
  7. 接下来,更新 show-prediction.html 的视图函数,使其能够遍历 image_file_name_list,为列表中的每个文件获取预测结果。将每个预测的 pred_class 值保存在一个名为 prediction_string_list 的列表中。

  8. 使用你在 步骤 5 中定义的 package_list 函数来准备 prediction_string_list,并将其发送到 show-prediction.html

        prediction_list = package_list("prediction_key",prediction_string_list)
    
  9. 更新视图函数的 return 语句,包含 prediction_list

        return(render_template('show-prediction.html',prediction_list=prediction_list))
    

    现在你已经完成了对 Flask 服务器的更新,以便处理多个图像文件。

  10. 接下来,更新 show-prediction.html,显示每个图像的预测结果:

      <h1 style="color: green"> 
        Here are the predictions for the images you selected:
      </h1> 
      <h1 style="color: green">
      <p> 
      {% for prediction in prediction_list %}
        {{prediction.prediction_key}}{% if not loop.last %}, {% endif %}
      {% endfor %}
      </p>  
      </h1>
    
  11. 现在测试是否一切正常。启动位于 deploy_image_test 目录中的 Flask 服务器:

    python web_flask_deploy_image_model.py
    
  12. 在浏览器中打开 localhost:5000 来显示 home.html。从 deploy_image_test/test_images 目录中选择 4_100.jpg5_100.jpg26_100.jpg 文件。选择这些文件后,home.html 会更新,显示已选择了三个文件,如 图 7.17 所示:图 7.17 – 选择三个图像文件后的 home.html

    图 7.17 – 选择三个图像文件后的 home.html

  13. 选择 show-predictions.html,如 图 7.18 所示:

图 7.18 – show-prediction.html 显示多个图像的预测结果

图 7.18 – show-prediction.html 显示多个图像的预测结果

恭喜!你已完成了一个有用的扩展,成功部署了图像分类模型。

第八章:第八章:扩展的 fastai 和部署功能

到目前为止,在本书中,你已经学习了如何使用 fastai 获取和探索数据集,如何使用表格数据、文本数据和图像数据集训练 fastai 模型,以及如何部署 fastai 模型。到目前为止,本书的重点一直是在尽可能多地展示 fastai 的功能,且主要使用 fastai 高级 API。特别是,我们强调了使用 dataloaders 对象作为定义用于训练模型的数据集的基础。在本书的这一部分,我们尽可能地遵循了理想路径。为了演示如何使用 fastai 完成任务,我们选择了最直接的方式。

在本章中,我们将从理想路径中走出一些步骤,探索 fastai 的额外功能。你将学习如何更密切地追踪模型的变化,如何控制训练过程,并且如何充分利用 fastai 提供的更多功能。我们还将介绍一些与模型部署相关的高级主题。

以下是本章将覆盖的食谱:

  • 获取更多关于使用表格数据训练的模型的详细信息

  • 获取更多关于图像分类模型的详细信息

  • 使用增强数据训练模型

  • 使用回调函数充分利用训练周期

  • 使你的模型部署对他人可用

  • 在图像分类模型部署中显示缩略图

  • 测试你的知识

技术要求

在本章中,你将同时使用云环境和本地环境来进行模型部署:

  • 确保你已完成第一章《快速入门 fastai》中的设置部分,并且拥有一个可用的 Gradient 实例或 Colab 设置。

  • 确保你已完成第七章《部署与模型维护》中的在本地系统上设置 fastai的步骤,以便在本地系统上设置 fastai。

确保你已从 github.com/PacktPublishing/Deep-Learning-with-fastai-Cookbook 克隆了本书的代码仓库,并且能够访问 ch8 文件夹。这个文件夹包含了本章中描述的代码示例。

获取更多关于使用表格数据训练的模型的详细信息

第三章《使用表格数据训练模型》中的使用 fastai 训练模型并使用精度作为度量指标的食谱中,你使用表格数据集训练了一个 fastai 模型,并使用了准确度作为评估指标。在本食谱中,你将学习如何为这个模型获取额外的指标:精确度召回率。精确度是指真正例除以真正例加上假正例的比例。召回率是指真正例除以真正例加上假负例的比例。

这些是有用的指标。例如,在本示例中,我们正在训练的模型预测个人收入是否超过 50,000 美元。如果要尽量避免假阳性 – 即在个人收入低于 50,000 美元时预测其收入超过该金额 – 那么我们希望精确率尽可能高。本示例将向您展示如何将这些有用的指标添加到 fastai 模型的训练过程中。

准备工作

确认您可以在您的 repo 的ch8目录中打开training_with_tabular_datasets_metrics.ipynb笔记本。

如何操作…

在这个示例中,您将运行training_with_tabular_datasets_metrics.ipynb笔记本。一旦您在您的 fastai 环境中打开笔记本,请完成以下步骤:

  1. 运行笔记本中的单元格,直到Define and train model单元格以导入所需的库、设置您的笔记本和准备数据集。

  2. 运行以下单元格以定义和训练模型:

    recall_instance = Recall()
    precision_instance = Precision()
    learn = tabular_learner(dls,layers=[200,100], metrics=[accuracy,recall_instance,precision_instance])
    learn.fit_one_cycle(3)
    

    本单元格中的关键项如下:

    a) recall_instance = Recall() – 定义一个召回率度量对象。请注意,如果您直接将Recall放入模型的指标列表中,将会出现错误。相反,您需要定义一个召回率度量对象,例如recall_instance,然后将该对象包含在指标列表中。有关此度量标准的更多详细信息,请参阅 fastai 文档(docs.fast.ai/metrics.html#Recall)。

    b) precision_instance = Precision() – 定义一个精确率度量对象。如果您直接将Precision放入指标列表中,将会出现错误,因此您需要首先定义precision_instance对象,然后将该对象包含在模型的指标列表中。

    c) metrics=[accuracy,recall_instance,precision_instance] – 指定模型将以准确率、召回率和精确率作为指标进行训练。

    该单元格的输出,如图 8.1所示,包括每个训练运行时的准确率以及召回率和精确率:

Figure 8.1 – 训练输出包括召回率和精确率

图 8.1 – 训练输出包括召回率和精确率

恭喜!您已经用表格数据训练了一个模型,并生成了召回率和精确率指标以用于训练过程。

工作原理…

您可能会问自己,我是如何知道您不能直接将RecallPrecision放入模型的指标列表中,并且需要先定义对象,然后将这些对象包含在指标列表中的。简单的答案是通过试错。具体来说,当我尝试直接将RecallPrecision放入模型的指标列表时,我遇到了以下错误:

TypeError: unsupported operand type(s) for *: 'AccumMetric' and 'int'

当我搜索这个错误时,我在 fastai 论坛上找到了这篇帖子:forums.fast.ai/t/problem-with-f1scoremulti-metric/63721。帖子解释了错误的原因,并指出为了解决这个问题,我需要先定义RecallPrecision对象,然后再将它们包含在 metrics 列表中。

这个经验既展示了 fastai 的一个弱点,也展示了它的一个优点。弱点是PrecisionRecall的文档缺少一个重要的细节——你不能直接将它们用于 metrics 列表。优点是 fastai 论坛提供了清晰准确的解决方案,解决了像这样的疑难问题,体现了 fastai 社区的力量。

获取有关图像分类模型的更多细节

第六章使用简单整理视觉数据集训练分类模型食谱中,你使用CIFAR整理数据集训练了一个图像分类模型。训练和训练模型的代码很简单,因为我们利用了 fastai 中的高级结构。在本食谱中,我们将重新审视这个图像分类模型,并探索 fastai 中的技术,以获取关于模型及其表现的更多信息,包括以下内容:

  • 检查 fastai 生成的数据预处理流水线

  • 获取训练过程中的训练和验证损失图表

  • 显示模型表现最差的图像

  • 显示混淆矩阵以获取模型表现不佳的情况快照

  • 将模型应用于测试集,并检查模型在测试集上的表现

在本食谱中,我们将扩展之前训练CIFAR整理数据集的食谱。通过利用 fastai 的附加功能,我们将能够更好地理解我们的模型。

做好准备

确认你可以在仓库的ch8目录中打开training_with_image_datasets_datablock.ipynb笔记本。

如何操作…

在本节中,你将运行training_with_image_datasets_datablock.ipynb笔记本。一旦你在 fastai 环境中打开了该笔记本,请完成以下步骤:

  1. 更新以下单元格,以确保model_path指向你在 Gradient 或 Colab 实例中的可写目录:

    model_path = '/notebooks/temp'
    
  2. 运行笔记本中的单元格,直到定义 DataBlock单元格,以导入所需的库,设置你的笔记本,并加载CIFAR数据集。

  3. 运行以下单元格以定义一个DataBlock对象。通过显式地定义一个DataBlock对象,我们将能够执行一些我们无法在dataloaders对象上直接执行的额外操作,比如获取流水线的摘要:

    db = DataBlock(blocks = (ImageBlock, CategoryBlock),
                     get_items=get_image_files,
                     splitter=RandomSplitter(seed=42),
                     get_y=parent_label)
    

    以下是此单元格中的关键内容:

    a) blocks = (ImageBlock, CategoryBlock) – 指定模型的输入为图像(ImageBlock),目标为对输入图像的分类(CategoryBlock)。

    b) get_items=get_image_files – 指定调用get_image_files函数来获取DataBlock对象的输入。

    c) splitter=RandomSplitter(seed=42) – 指定如何从训练集中定义验证集。默认情况下,20%的训练集被随机选择构成验证集。通过为seed指定值,此调用RandomSplitter将在多次运行中产生一致的结果。有关RandomSplitter的更多细节,请参阅文档(docs.fast.ai/data.transforms.html#RandomSplitter)。

    d) get_y=parent_label – 指定图像的标签(即图像所属的类别)由图像所在目录在输入数据集中的位置定义。例如,在 Gradient 上,训练集中的猫图像位于/storage/data/cifar10/train/cat目录下。

  4. 运行以下代码单元,使用在前一个代码单元中创建的DataBlock对象db来定义一个dataloaders对象:

    dls = db.dataloaders(path/'train',bs=32)
    

    以下是此代码单元中的关键项:

    a) db.dataloaders – 指定使用DataBlock对象db来创建dataloaders对象

    b) path/'train' – 指定此模型的输入仅为CIFAR数据集的训练子集。

  5. 运行以下代码单元来获取管道的概述:

    db.summary(path/"train")
    

    让我们看一下此代码单元输出的关键部分。首先,输出展示了有关输入数据集的详细信息,包括源目录、整个数据集的大小以及训练集和验证集的大小,如下图所示:

    图 8.2 – 输入数据集的概述

    接下来,输出展示了 fastai 应用于单个输入样本的管道,包括样本的源目录、为该样本创建的图像对象以及样本的标签(类别),如图 8.3所示:

    图 8.3 – 单个图像文件的管道概述

    图 8.3 – 单个图像文件的管道概述

    接下来,输出展示了 fastai 应用于构建单个批次的管道,即将从样本管道中输出的图像对象转换为张量。如图 8.4所示,32 x 32 像素的图像对象被转换为 3 x 32 x 32 的张量,其中第一维包含图像的颜色信息:

    图 8.4 – 应用于单个批次的管道的概述

    图 8.4 – 应用于单个批次的管道的概述

    最后,输出展示了应用于整个批次的变换,如图 8.5所示:

    图 8.5 – 应用于所有批次的流水线总结描述

    图 8.5 – 应用于所有批次的流水线总结描述

  6. 运行以下单元格以为测试集定义一个 DataBlock 对象:

    db_test = DataBlock(blocks = (ImageBlock, CategoryBlock),
                     get_items=get_image_files,
                     splitter=RandomSplitter(valid_pct=0.99,seed=42),
                     get_y=parent_label)
    

    请注意,与训练集的 DataBlock 对象不同,我们为 db_test 定义了一个明确的 valid_pct 值。我们将此值设置为 99%,因为在将测试集应用于模型时我们不会进行任何训练,因此无需将测试集的一部分用于训练。我们没有将 valid_pct 设置为 1.0,因为这个值会在你对 db_test 应用汇总时产生错误。

  7. 运行笔记本中的单元格直到 定义并训练模型 单元格以检查数据集。

  8. 运行以下单元格以使用 cnn_learner 对象定义模型。请注意,由于你从 DataBlock 对象定义了一个 dataloaders 对象,你将获得两全其美的效果:既有 DataBlock 对象特有的附加功能(例如汇总),又有你在本书中大多数食谱中使用的 dataloaders 对象的熟悉代码模式:

    learn = cnn_learner(dls, resnet18, 
                        loss_func=LabelSmoothingCrossEntropy(), 
                        metrics=accuracy)
    
  9. 运行以下单元格以训练模型:

    learn.fine_tune(2,cbs=ShowGraphCallback())
    

    请注意 cbs=ShowGraphCallback() 参数。使用该参数,训练过程的输出包括训练和验证损失的图形,如 图 8.6 所示:

    图 8.6 – 训练和验证损失图

    图 8.6 – 训练和验证损失图

    该图表包含与从训练过程中默认获得的训练结果表相同的数据,如 图 8.7 所示:

    图 8.7 – 训练和验证损失表

    图 8.7 – 训练和验证损失表

  10. 运行以下单元格以保存训练好的模型。我们暂时将模型的路径更新为一个在 Gradient 中可写的目录,以便我们能够保存模型:

    save_path = learn.path
    learn.path = Path(model_path)
    learn.save('cifar_save_'+modifier)
    learn.path = save_path
    

    这个单元格中的关键项如下:

    a) save_path = learn.path – 指定将当前模型路径保存到 save_path

    b) learn.path = Path(model_path) – 指定将模型路径设置为可写目录。

    c) learn.save('cifar_save_'+modifier) – 保存模型。稍后我们将加载保存的模型,并使用测试集对其进行验证。

    d) learn.path = save_path – 将模型的路径重置为其原始值。

  11. 运行以下单元格以确认训练模型的准确性表现:

    learn.validate()
    

    输出中的第二个值应与在训练的最后一个时期看到的准确度相匹配,如 图 8.8 所示:

    图 8.8 – 验证输出

    图 8.8 – 验证输出

  12. 运行单元格直到 检查顶级损失示例和混淆矩阵 单元格。

  13. 运行以下单元格以查看模型损失最大的样本:

    interp = ClassificationInterpretation.from_learner(learn)
    interp.plot_top_losses(9, figsize=(15,11))
    

    这个单元格中的关键项如下:

    a) interp = ClassificationInterpretation.from_learner(learn) – 指定interplearn模型的解释对象

    b) interp.plot_top_losses(9, figsize=(15,11)) – 指定显示损失最大的九张图片

    输出显示了模型在预测时损失最大的图像示例,以及图像的预测内容和实际内容。你可以将这些图像视为模型预测最差的图像。图 8.9展示了输出的一个子集。例如,对于显示的第一张图像,模型预测图像包含一只鸟,而图像实际上标记为猫:

    图 8.9 – 最大损失的图像示例

    图 8.9 – 最大损失的图像示例

  14. 运行以下单元格以生成训练模型的混淆矩阵:

    interp.plot_confusion_matrix()
    

    该单元格的输出是一个混淆矩阵,用于总结训练模型的性能,如图 8.10所示。混淆矩阵是一个N x N的矩阵,其中N是目标类别的数量。它将实际的目标类别值(纵轴)与预测值(横轴)进行比较。矩阵的对角线显示了模型做出正确预测的情况,而对角线之外的所有条目则是模型做出错误预测的情况。例如,在图 8.10所示的混淆矩阵中,在 138 个实例中,模型将一张狗的图片预测为猫,而在 166 个实例中,它将一张猫的图片预测为狗:

    图 8.10 – 训练模型的混淆矩阵

    图 8.10 – 训练模型的混淆矩阵

  15. 现在你已经检查了模型在训练集上的表现,接下来让我们检查模型在测试集上的表现。为此,我们将使用测试集定义一个新的dataloaders对象,定义一个基于该dataloaders对象的模型,加载训练模型的保存权重,然后执行与训练集上训练的模型相同的步骤来评估模型性能。首先,运行以下单元格以创建一个新的dataloaders对象dls_test,该对象是使用测试数据集定义的:

    dls_test = db_test.dataloaders(path/'test',bs=32)
    
  16. 运行以下单元格以定义一个新的模型对象learn_test,该对象基于你在前一步创建的dataloaders对象。注意,模型定义与在步骤 8中为训练集定义的模型完全相同,只是它使用了在前一步中用测试数据集定义的dataloaders对象dls_test

    learn_test = cnn_learner(dls_test, resnet18, 
                        loss_func=LabelSmoothingCrossEntropy(), 
                        metrics=accuracy)
    
  17. 运行以下单元格以加载使用训练集训练的模型的保存权重:

    learn_test.path = Path(model_path)
    learn_test.load('cifar_save_'+modifier)
    

    以下是该单元格中的关键项:

    a) learn_test.path = Path(model_path) – 指定learn_test模型的路径更改为保存模型权重的目录,该目录在步骤 10中定义

    b) learn_test.load('cifar_save_'+modifier') – 指定learn_test模型加载来自使用训练集训练的模型的权重

    现在我们准备好使用测试集来测试模型了。

  18. 运行以下单元格以查看模型在测试集上的总体准确度:

    learn_test.validate()
    

    输出中的第二个值是模型在测试集上的准确度,如图 8.11所示:

    图 8.11 – 在测试集上验证的输出

    图 8.11 – 在测试集上验证的输出

  19. 运行以下单元格以查看测试集中模型损失最大的一些图像:

    interp_test = ClassificationInterpretation.from_learner(learn_test)
    interp_test.plot_top_losses(9, figsize=(15,11))
    

    输出显示了测试集中损失最大的一些图像示例,以及图像的预测内容和实际内容。图 8.12显示了输出的一部分:

    图 8.12 – 测试集中模型表现最差的样本图像

    图 8.12 – 测试集中模型表现最差的样本图像

  20. 运行以下单元格以获取应用于测试集的模型的混淆矩阵:

    interp_test.plot_confusion_matrix()
    

    该单元格的输出是一个混淆矩阵,汇总了模型在测试集上的表现,如图 8.13所示。请注意,这个混淆矩阵中的数字比应用于训练集的模型的混淆矩阵中的数字要小:

图 8.13 – 应用在测试集上的模型的混淆矩阵

图 8.13 – 应用在测试集上的模型的混淆矩阵

恭喜!你已经完成了图像分类模型的工作,见证了 fastai 所提供的附加信息的好处。你还学会了如何将模型应用于整个测试集,并检查模型在测试集上的表现。

它是如何工作的……

将本节中创建的模型与第六章**,使用视觉数据训练模型配方中创建的模型进行比较是很有启发性的。

以下是来自第六章**,使用视觉数据训练模型配方中的dataloaders对象的定义:

dls = ImageDataLoaders.from_folder(path, train='train', valid='test')

这里是这个配方中dataloaders对象的定义。与之前的dataloaders定义不同,这个定义使用了DataBlock对象db

dls = db.dataloaders(path/'train',bs=32)

以下是DataBlock对象db的定义:

db = DataBlock(blocks = (ImageBlock, CategoryBlock),
                 get_items=get_image_files,
                 splitter=RandomSplitter(seed=42),
                 get_y=parent_label)

使用DataBlock对象定义dataloaders对象有什么好处?

首先,从DataBlock对象开始,你可以更好地控制数据集的详细设置。你可以显式定义用于定义输入数据集的函数(赋值给get_items的函数),以及用于定义标签的函数(赋值给get_y的函数)。你可能还记得,在第六章《使用视觉数据训练模型》的使用多图像分类模型与精心策划的视觉数据集训练示例中,我们利用了这种灵活性。在那个例子中,我们需要确保输入数据集排除没有注释的图像。通过在那个例子中使用DataBlock对象,我们能够定义一个自定义函数并赋值给get_items,从而排除了没有注释的图像。

其次,如果我们有一个DataBlock对象,可以利用 fastai 的一些附加功能。在这个例子中,我们能够对DataBlock对象应用summary()函数,查看 fastai 对输入数据集应用的处理流程。summary()函数不能应用于dataloaders对象,因此如果没有定义DataBlock对象,我们将错过有关数据处理流程的额外细节。

如果DataBlock对象如此有用,为什么在第六章《使用视觉数据训练模型》的使用简单的精心策划视觉数据集训练分类模型示例中没有使用它呢?在那个例子中,我们只使用了dataloaders对象(而不是从DataBlock对象开始),因为那个例子相对简单——我们不需要DataBlock对象的额外灵活性。在整本书中,我们尽可能使用 fastai 的最高级 API,包括在那个例子中。简洁性是 fastai 的一个关键优势,因此如果可以使用最高级的 API(包括直接使用dataloaders),那么保持简单并坚持使用最高级的 API 是合乎逻辑的。

使用增强数据训练模型

在前面的例子中,你了解了 fastai 提供的一些额外功能,以跟踪你的模型,并学习了如何将测试集应用于在训练集上训练的模型。在这个例子中,你将学习如何将这些技术与 fastai 使其易于融入模型训练的另一项技术结合起来:数据增强。通过数据增强,你可以扩展训练数据集,包含原始训练样本的变体,并可能提高训练模型的性能。

图 8.14 显示了对来自CIFAR数据集的图像应用增强的结果:

图 8.14 – 应用于图像的增强

图 8.14 – 应用于图像的增强

你可以在这些示例中看到,应用到图像上的增强操作包括在垂直轴上翻转图像、旋转图像以及调整图像的亮度。

与之前的教程类似,本教程中我们将使用在CIFAR精选数据集上训练的图像分类模型,但本教程中我们将尝试对数据集中的图像进行增强。

准备工作

确认你能在你的仓库的ch8目录中打开training_with_image_datasets_datablock_augmented.ipynb笔记本。

如何执行...

在本教程中,你将运行training_with_image_datasets_datablock_augmented.ipynb笔记本。以下是你在本教程中将要完成的高层次概述:

  1. 使用未增强的数据训练模型。

  2. 使用增强的数据训练模型。

  3. 使用未增强的数据的模型对测试集进行测试。

  4. 使用增强数据训练的模型对测试集进行测试。

一旦你在 fastai 环境中打开了笔记本,按照以下步骤完成操作:

  1. 更新以下单元格,确保model_path指向你在 Gradient 或 Colab 实例中可写的目录:

    model_path = '/notebooks/temp'
    
  2. 运行笔记本中的单元格,直到尝试增强训练集单元格,以导入所需的库、设置笔记本并在未增强的CIFAR数据集上训练模型。

  3. 运行以下单元格以创建一个新的DataBlock对象db2,该对象包含增强功能,并定义了一个新的dataloaders对象dls2

    db2 = db.new(batch_tfms=aug_transforms())
    dls2 = db2.dataloaders(path/'train',bs=32)
    

    该单元格中的关键内容如下:

    a) db2 = db.new(batch_tfms=aug_transforms()) – 定义了一个新的DataBlock对象db2,该对象包含由aug_transforms()定义的默认增强。有关aug_transforms()的详细信息,请参见 fastai 文档:docs.fast.ai/vision.augment.html#aug_transforms

    b) dls2 = db2.dataloaders(path/'train',bs=32) – 基于DataBlock对象db2定义了一个新的dataloaders对象dls2。现在,dls2定义了输入数据集的训练子集。

  4. 运行以下单元格以获取处理流程的总结:

    db2.summary(path/"train")
    

    此单元格的输出为我们提供了关于应用到数据集的处理流程的详细信息。首先,输出显示了输入数据集的详细信息,包括源目录、整个数据集的大小以及训练集和验证集的大小,如图 8.15所示:

    图 8.15 – 输入数据集的总结描述

    图 8.15 – 输入数据集的总结描述

    接下来,输出展示了 fastai 对单个输入样本应用的处理流程,包括样本的源目录、为样本创建的图像对象以及样本的标签(类别),如图 8.16所示:

    图 8.16 – 单个图像文件的处理流程总结描述

    图 8.16 – 单个图像文件的管道概述描述

    接下来,输出显示了 fastai 应用于构建单个批次的管道,即将从样本管道输出的图像对象转换为张量。如图 8.17所示,32 x 32 像素的图像对象被转换为 3 x 32 x 32 的张量,其中第一维包含图像的颜色信息:

    图 8.17 – 应用于单个批次的管道概述描述

    图 8.17 – 应用于单个批次的管道概述描述

    输出的前三部分与获取更多关于图像分类模型的细节食谱中summary()的输出的相同部分一致。然而,最后一部分不同,它描述了在增强过程中应用于图像的变换,如图 8.18所示:

    图 8.18 – 数据处理管道描述(包括增强变换)

    图 8.18 – 应用于所有批次的管道描述(包括增强变换)

  5. 运行以下单元格以查看应用于数据集中图像的增强变换示例:

    dls2.train.show_batch(unique=True, max_n=8, nrows=2)
    

    unique=True参数指定我们希望看到应用于单个图像的增强示例。

    该单元格的输出示例如图 8.19所示:请注意增强图像的变化,包括在垂直轴上翻转、亮度变化和卡车占据垂直空间的程度变化:

    图 8.19 – 图像的增强版本

    图 8.19 – 图像的增强版本

  6. 运行以下单元格来定义一个模型,以便使用增强数据集进行训练:

    learn2 = cnn_learner(dls2, resnet18, 
                        loss_func=LabelSmoothingCrossEntropy(), 
                        metrics=accuracy)
    
  7. 运行以下单元格来使用增强数据集训练模型:

    learn2.fine_tune(2)
    

    该单元格的输出显示了训练损失、验证损失和验证准确率,如图 8.20所示:

    图 8.20 – 使用增强数据集训练模型的输出

    图 8.20 – 使用增强数据集训练模型的输出

  8. 运行以下单元格来保存使用增强数据集训练后的模型:

    save_path = learn2.path
    learn2.path = Path(model_path)
    learn2.save('cifar_augmented_save_'+modifier)
    learn2.path = save_path
    
  9. 运行单元格直到检查使用增强数据集训练的模型在测试集上的表现单元格,以了解使用未增强数据集训练的模型在测试数据集上进行预测的表现。

  10. 运行以下单元格来定义一个与测试集关联的dataloaders对象dls_test

    dls_test = db_test.dataloaders(path/'test',bs=32)
    
  11. 运行以下单元格来定义learn_augment_test模型,以便在测试数据集上进行测试:

    learn_augment_test = cnn_learner(dls_test, resnet18, 
                        loss_func=LabelSmoothingCrossEntropy(), 
                        metrics=accuracy)
    
  12. 运行以下单元格来加载learn_augment_test模型,并使用从增强数据集训练模型时保存的权重:

    learn_augment_test.path = Path(model_path)
    learn_augment_test.load('cifar_augmented_save_'+modifier)
    

    现在我们有一个learn_augment_test学习器对象,它包含了使用增强数据集训练后的权重,并与测试数据集关联。

  13. 运行以下单元格,查看learn_augment_test模型在测试集上的整体准确性:

    learn_augment_test.validate()
    

    此单元格的输出显示了该模型在测试集上的准确性,如图 8.21所示:

    图 8.21 – 在增广数据集上训练的模型应用于测试集的准确性

    图 8.21 – 在增广数据集上训练的模型应用于测试集的准确性

  14. 运行以下单元格,获取在增广数据集上训练的模型在测试集上损失最大时的图像示例:

    interp_augment_test = ClassificationInterpretation.from_learner(learn_augment_test)
    interp_augment_test.plot_top_losses(9, figsize=(15,11))
    

    此单元格的输出显示了在增广数据集上训练的模型在测试集上损失最大的图像。图 8.22显示了这个输出结果的示例:

    图 8.22 – 模型在增广数据集上训练时损失最大的图像示例    增广数据集的损失最大

    图 8.22 – 模型在增广数据集上训练时损失最大的图像示例

  15. 运行以下单元格,查看在增广数据集上训练的模型应用于测试集的混淆矩阵:

    interp_augment_test.plot_confusion_matrix()
    

    此单元格的输出是图 8.23所示的混淆矩阵:

图 8.23 – 在增广数据集上训练的模型应用于测试集的混淆矩阵

图 8.23 – 在增广数据集上训练的模型应用于测试集的混淆矩阵

恭喜你!你已经使用增广数据集训练了一个 fastai 图像分类模型,并在测试集上进行了验证。

它是如何工作的…

在本示例中,我们详细讲解了如何在增广数据集上训练图像分类模型。我们在这个示例中使用的笔记本还包括了在非增广数据集上训练图像分类模型。现在让我们比较这两个模型的性能,看看使用增广数据集是否值得。

如果我们比较两个模型的训练结果,如图 8.24所示,训练于非增广数据集上的模型似乎表现更好:

图 8.24 – 使用和不使用增广数据训练时的结果对比

图 8.24 – 使用和不使用增广数据训练时的结果对比

现在让我们比较这两个模型在测试数据集上的表现。图 8.25显示了在增广和非增广数据集上训练的模型应用于测试集时validate()的输出结果。再次强调,训练于非增广数据集上的模型似乎表现更好:

图 8.25 – 使用和不使用增广数据训练时结果的对比

图 8.25 – 使用和不使用增广数据训练时validate()结果的对比

图 8.26显示了在增广数据集和非增广数据集上训练的模型应用于测试集时的混淆矩阵:

图 8.26 – 使用和不使用增强数据训练时混淆矩阵的对比

图 8.26 – 使用和不使用增强数据训练时混淆矩阵的对比

总体而言,使用增强数据集训练的模型在测试集上的表现似乎并不比使用非增强数据训练的模型更好。这可能令人失望,但没关系。

本教程的目的是向你展示如何利用 fastai 中的增强数据功能,而不是进行深入的分析以获得增强数据的最佳结果。并非所有应用都会从数据增强中受益,我们只尝试了默认的增强方法。fastai 为图像模型提供了广泛的增强选项,具体可以参考 fastai 文档:docs.fast.ai/vision.augment.html。可能这个数据集具有某些特性(例如相对较低的分辨率),导致数据增强效果不佳。也有可能默认的 fastai 增强方法中的增强操作集并不适合这个数据集,使用自定义的增强操作集可能会得到更好的结果。

aug_transforms() 这个函数是如何增强训练集中的图像的?我们可以通过对比使用非增强训练集训练的模型与使用增强数据训练的模型的流水线摘要来获得线索。图 8.27 显示了非增强型 DataBlock 对象的 summary() 输出的最后部分:

图 8.27 – 非增强型 DataBlock 对象的摘要输出

图 8.27 – 非增强型 DataBlock 对象的摘要输出

图 8.28 显示了增强型 DataBlock 对象的 summary() 输出的最后部分:

图 8.28 – 非增强型 DataBlock 对象的摘要输出

图 8.28 – 非增强型 DataBlock 对象的摘要输出

通过对比这两个模型的摘要输出部分,你可以了解在增强数据集上应用了哪些变换,如 图 8.29 所示:

图 8.29 – 在  中应用的变换

图 8.29 – 在 aug_transforms() 中应用的变换

现在,你已经了解了使用增强数据集与非增强数据集训练图像分类模型之间的一些区别。

使用回调函数来最大化你的训练周期的效果

到目前为止,在本书中,我们已经通过将fit_one_cyclefine_tune应用于学习器对象来启动 fastai 模型的训练过程,并且让训练按照指定的轮次运行。对于我们在本书中训练的许多模型,这种方法已经足够好,特别是对于每个周期需要很长时间的模型,而我们只训练一个或两个周期。但如果我们希望训练模型 10 个周期或更多呢?如果我们只是让训练过程一直运行到结束,我们将面临图 8.30中显示的问题。在图 8.30中,我们可以看到使用metric设置为accuracy训练一个表格模型 10 个周期的结果:

图 8.30 – 训练一个表格数据模型的 10 轮训练结果

图 8.30 – 训练一个表格数据模型的 10 轮训练结果

这个训练过程的目标是得到一个具有最佳准确度的模型。考虑到这一目标,图 8.30中显示的结果存在两个问题:

  1. 最佳准确度出现在第 5 轮,但我们在训练过程结束时得到的模型是最后一轮的准确度。对于这个模型的validate()输出,如图 8.31所示,显示该模型的准确度不是训练过程中最佳的准确度:图 8.31 – 使用 10 轮训练模型后的输出

    图 8.31 – 使用 10 轮训练模型后validate()的输出

    注意,如果准确度在训练结束时仍然稳步提高,则模型可能还没有过拟合,尽管你需要通过使用测试集对已训练的模型进行验证来确认这一点。

  2. 训练在准确度不再提高后仍然继续进行,因此浪费了训练能力。对于像这样每个周期不到一分钟就完成的训练来说,这不算什么问题。然而,想象一下,如果每个周期都需要一个小时,而你正在使用付费的 Gradient 实例进行训练,那么浪费的训练周期可能会花费大量的钱,而这些周期并没有提高模型的性能。

幸运的是,fastai 提供了解决这两个问题的方法:回调函数。你可以使用回调函数来控制训练过程,并在训练期间自动采取行动。在本节中,你将学习如何在 fastai 中指定回调函数,当模型没有进一步改善时,停止训练过程并保存训练过程中最佳的模型。你将重新回顾在第三章**,使用表格数据训练模型中的使用整理过的表格数据训练模型一节中训练的模型,但这次你将使用 fastai 回调函数来控制模型的训练过程。

准备就绪

确认你可以在你的仓库的ch8目录中打开training_with_tabular_datasets_callbacks.ipynb笔记本。

如何操作…

在这个食谱中,你将运行training_with_tabular_datasets_callbacks.ipynb笔记本。在这个食谱中,你将训练模型的三个不同变体:

  • 无回调的训练

  • 使用单个回调,在训练过程停止改进时结束训练

  • 使用两个回调进行训练:一个用于在训练停止改进时结束训练,另一个用于保存最佳模型

一旦你在 fastai 环境中打开笔记本,完成以下步骤:

  1. 更新以下单元格,确保model_path指向你在 Gradient 或 Colab 实例中可写的目录:

    model_path = '/notebooks/temp'
    
  2. 运行笔记本中的单元格,直到Define and train the model with no callbacks单元格,导入所需的库,设置你的笔记本,并为ADULT_SAMPLE数据集定义一个dataloaders对象。

    通过这种方式调用set_seed()并在每次训练运行之前定义一个dataloaders对象,我们可以获得跨多个训练运行的一致训练结果。当我们比较有回调和没有回调的训练结果时,你将看到保持一致的训练结果的重要性。

  3. 运行以下单元格以定义并训练一个没有回调的模型:

    %%time
    set_seed(dls,x=42)
    learn = tabular_learner(dls,layers=[200,100], metrics=accuracy)
    learn.fit_one_cycle(10)
    

    set_seed()的调用指定了与模型相关的随机种子被设置为42(一个任意值),并且结果是可重复的,这正是我们所希望的。

    该单元格的输出显示了每一轮的训练损失、验证损失和准确率,以及整个训练运行所花费的时间,正如图 8.32所示。请注意,训练运行进行了所有 10 轮,尽管准确率在第 2 轮之后停止改进:

    图 8.32 – 使用没有回调的模型训练结果

    图 8.32 – 使用没有回调的模型训练结果

  4. 运行以下单元格以获取训练模型的准确率:

    learn.validate()
    

    该单元格的输出显示在图 8.33中。模型的准确率是在第 9 轮,也就是训练运行的最后一轮,达到的准确率:

    图 8.33 – 使用没有回调的模型进行验证时的输出

    图 8.33 – 使用没有回调的模型进行验证时的输出

  5. 运行以下单元格以定义并训练一个带有一个回调的模型——当模型的准确率不再改进时进行早停:

    %%time
    set_seed(dls,x=42)
    learn_es = tabular_learner(dls,layers=[200,100], metrics=accuracy)
    learn_es.fit_one_cycle(10,cbs=EarlyStoppingCallback(monitor='accuracy', min_delta=0.01, patience=3))
    

    这个模型的fit语句包括了EarlyStoppingCallback回调的定义。以下是定义回调时使用的参数:

    a) monitor='accuracy' – 指定回调正在跟踪准确率指标的值。当准确率指标在指定的时间段内停止改进时,将触发该回调。

    b) min_delta=0.01 – 指定回调函数关注的准确率变化至少为 0.01。也就是说,如果准确率在两个周期之间的变化小于 0.01,则回调函数将忽略这种变化。

    c) patience=3 – 指定回调函数在准确率指标在 3 个周期内不再改善时触发。

    该单元格的输出显示了每个周期的训练损失、验证损失和准确率,以及训练运行所需的总时间,如 图 8.34 所示:

    图 8.34 – 使用早期停止回调函数训练模型的结果

    图 8.34 – 使用早期停止回调函数训练模型的结果

    请注意,现在训练在第 5 个周期之后停止了。您可以看到回调函数跟踪的指标 accuracy 在第 2 个周期之后不再改善。一旦过了另外 3 个周期(回调函数的 patience 参数设置为的值),训练过程将提前停止,在第 5 个周期停止,即使 fit_one_cycle() 调用指定了运行 10 个周期。因此,使用早期停止回调函数,我们保存了 4 个周期和约 25%的训练时间(使用早期停止回调函数为 51 秒,而没有回调函数为 68 秒)。

  6. 运行以下单元格以获取经过训练模型的准确率:

    learn_es.validate()
    

    该单元格的输出显示在 图 8.35 中。模型的准确率是在第 5 个周期(训练运行的最后一个周期)中实现的准确率:

    图 8.35 – 使用早期停止回调函数训练的模型的验证输出

    图 8.35 – 使用早期停止回调函数训练的模型的验证输出

  7. 使用早期停止回调函数,相比没有回调函数的模型,我们减少了训练时间,但训练得到的模型准确率低于训练运行期间达到的最佳准确率。让我们训练另一个模型,其中包括一个回调函数来保存最佳模型。该回调函数将确保经过训练的模型的准确率是我们从训练运行中获得的最佳结果。要做到这一点,请从运行以下单元格开始定义并训练一个包括两个回调函数的模型 – 当模型的准确率不再改善时提前停止训练,并保存最佳模型:

    %%time
    set_seed(dls,x=42)
    learn_es_sm = tabular_learner(dls,layers=[200,100], metrics=accuracy)
    keep_path = learn_es_sm.path
    # set the model path to a writeable directory. If you don't do this, the code will produce an error on Gradient
    #learn_es_sm.path = Path('/notebooks/temp/models')
    learn_es_sm.path = Path(model_path)
    learn_es_sm.fit_one_cycle(10,cbs=[EarlyStoppingCallback(monitor='accuracy', min_delta=0.01, patience=3),SaveModelCallback(monitor='accuracy', min_delta=0.01)])
    # reset the model path
    learn_es_sm.path = keep_path
    

    除了像 步骤 5 中指定的 EarlyStoppingCallback 回调函数的定义之外,此模型的 fit 语句还包括一个 SaveModelCallback 回调函数。这里用来定义此回调函数的参数如下:

    a) monitor='accuracy' – 指定 SaveModelCallback 回调函数跟踪准确率指标的值。当准确率达到新的最高水平时,模型将被保存。

    b) min_delta=0.01 – 指定回调函数关注的准确率变化至少为 0.01。

    请注意,该单元还包括调整模型路径为 Gradient 可写目录的语句。如果不将模型路径更改为可写目录,运行该单元时会在 Gradient 中出现错误。同时请注意,当你运行此单元时,可能会看到保存的文件不包含优化器状态的警告信息——在本教程中,你无需担心此消息。

    该单元的输出显示了每轮训练损失、验证损失和准确度,以及训练运行所花费的总时间,如图 8.36所示。请注意,由于早停回调,训练在第 5 轮结束:

    图 8.36 – 使用早停回调和模型保存回调训练模型的结果

    图 8.36 – 使用早停回调和模型保存回调训练模型的结果

  8. 运行以下单元以获取训练模型的准确度:

    learn_es.validate()
    

    该单元的输出如图 8.37所示。模型的准确度是在第 2 轮迭代中的准确度,是训练过程中最好的准确度:

图 8.37 – 使用早停和模型保存回调训练的模型的 validate() 输出

图 8.37 – 使用早停和模型保存回调训练的模型的 validate() 输出

通过使用这两个回调,我们避免了运行不会提高模型性能的迭代,并最终得到了在训练过程中最佳表现的训练模型。

恭喜!你已经成功应用了 fastai 的回调来优化训练过程,从而在训练周期中最大化你的模型表现。

它是如何工作的……

本教程演示了如何使用 fastai 的回调来控制训练过程,以便从系统的计算能力和训练时间中获得最佳结果。关于 fastai 回调,还有一些额外的细节值得回顾。在本节中,我们将深入探讨 fastai 中回调的使用方式。

set_seed() 函数并非 fastai 的默认函数

为了清楚地比较有回调和没有回调时模型的表现,我们需要控制训练运行之间的随机差异。也就是说,我们希望多次训练模型,并在不同的训练运行之间获得一致的损失和准确度。例如,如果第一次训练运行的第 1 轮准确度为 0.826271,我们希望每次后续训练运行的第 1 轮准确度完全相同。通过确保训练运行之间表现一致,我们可以进行公平对比,聚焦于回调的影响,而不是训练运行之间的随机差异。

在本配方中,我们使用了 set_seed() 函数来控制训练运行之间的随机差异。你可能已经注意到,虽然 fastai 文档中包含了一个 set_seed() 函数(docs.fast.ai/torch_core.html#set_seed),但我们在配方中并没有使用它。相反,我们使用了以下这个函数,它是从论坛讨论中分享的代码中修改过来的——github.com/fastai/fastai/issues/2832

def set_seed(dls,x=42): 
    random.seed(x)
    dls.rng.seed(x) 
    np.random.seed(x)
    torch.manual_seed(x)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False
    if torch.cuda.is_available():
        torch.cuda.manual_seed_all(x)

为什么要使用这个自定义的 set_seed() 函数,而不是 fastai 文档中提供的默认 set_seed() 函数?简单的原因是默认的 set_seed() 函数无法按预期工作——我没有得到一致的训练结果。而使用上面列出的 set_seed() 函数,我得到了稳定的一致性训练结果。

summary() 的回调部分没有包括在配方中定义的回调。

你可能会注意到,training_with_tabular_datasets_callbacks.ipynb 笔记本的末尾包含了对三个训练模型的学习者对象调用 summary() 函数的代码。这三个模型在配方中进行了训练。你可能预期,包含回调的 learn_eslearn_es_sm 模型的 summary() 输出中的 Callbacks 部分会列出早停和模型保存回调,但事实并非如此。图 8.38 展示了两个模型(它们显式定义了回调)的 Callbacks 部分,而这一部分与没有回调的模型的 Callbacks 部分是相同的:

图 8.38 – summary() 输出中的回调部分

图 8.38 – summary() 输出中的回调部分

图 8.38 – summary() 输出中的回调部分

为什么 summary() 输出中的 Callbacks 部分不包括模型中定义的回调?summary() 函数仅列出 fastai 本身定义的回调,而不是你自己定义的回调。

在 fastai 中,你还能做些什么与回调相关的操作?

在本配方中,我们使用了回调来确保训练过程尽可能高效,但这只是你在 fastai 中使用回调的一小部分功能。fastai 框架支持多种回调,用于控制训练过程的各个方面。事实上,在本书中,你已经接触到几种不同类型的 fastai 回调:

  • 追踪回调 – 我们在本配方中使用的早停和模型保存回调就是追踪回调的例子。这类回调的文档可以在这里找到:docs.fast.ai/callback.tracker.html

  • 使用 ShowGraphCallback 回调来显示训练和验证损失的图表。进度和日志回调的文档可以在这里找到:docs.fast.ai/callback.progress.html

  • 使用to_fp16()来指定你在该部分训练的语言模型的混合精度训练。有关混合精度训练的回调文档,请参见:docs.fast.ai/callback.fp16.html

到目前为止,通过本书中的配方所使用的回调函数展示了使用回调函数与 fastai 结合时,你可以获得的一些强大功能和灵活性。

让你的模型部署对其他人可用

第七章《部署和模型维护》中,你部署了几个 fastai 模型。为了获取预测结果,你将浏览器指向了localhost:5000,这会打开home.html,在这里你设置了评分参数,请求了预测,并在show-prediction.html中得到了预测结果。所有这些操作都是在本地系统上完成的。通过第七章《部署和模型维护》中的 Web 部署,你只能在本地系统上访问部署,因为localhost只在本地系统上可访问。如果你想将这些部署与朋友分享,让他们在自己的电脑上尝试你的模型,你该怎么办?

最简单的方法是使用一种叫做localhost的工具,在你的电脑上与其他电脑上的人一起工作。在这个配方中,我们将通过一些步骤来展示如何使用 ngrok 让你的部署对其他人可用。

准备工作

按照dashboard.ngrok.com/get-started中的说明设置一个免费的 ngrok 帐户,并在本地系统上设置 ngrok。记下你安装 ngrok 的目录—你将在配方中需要它。

如何实现…

借助 ngrok,你可以获得一个可以与朋友分享的 URL,这样他们就可以尝试你的 fastai 模型部署。本配方将向你展示如何分享表格模型的部署。

要分享你在第七章《部署和模型维护》中创建的部署,按照以下步骤进行:

  1. 将安装 ngrok 的目录设置为当前目录。

  2. 在命令行/终端中输入以下命令:

    https Forwarding URL:![Figure 8.39 – Output of ngrok    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-fasiai-cb/img/B16216_8_039.jpg)Figure 8.39 – Output of ngrok
    
  3. 通过将deploy_tabular设置为当前目录并输入以下命令,启动表格模型的部署:

    python web_flask_deploy.py
    
  4. 在另一台计算机上,打开浏览器并访问你在步骤 2中复制的https Forwarding URL。你应该能看到home.html,如图 8.40所示:图 8.40 – home.html

    图 8.40 – home.html

  5. home.html中,选择Get prediction并确认你在show-prediction.html中看到了预测结果,如图 8.41所示:

图 8.41 – show-prediction.html

图 8.41 – show-prediction.html

恭喜你!你已经成功共享了你的 fastai 模型部署,使其可以通过你分享的 ngrok 转发 URL 供其他系统的用户访问。

它是如何工作的…

当你运行 ngrok 时,它会在你的本地系统上创建一个安全隧道到localhost。当你分享 ngrok 返回的转发 URL 时,接收 URL 的人可以将浏览器指向该 URL,以查看你在本地系统上提供的内容。

当你启动 ngrok 时,你指定了 ngrok 隧道指向的端口。例如,在本教程中你输入了以下命令,指定 ngrok 转发的 URL 指向localhost:5000

.\ngrok http 5000

现在你对 ngrok 如何方便地帮助你与其他系统上的用户分享你的模型部署有了了解。

在图像分类模型部署中显示缩略图

当你在第七章《部署和模型维护》中为图像分类模型创建部署时,缺少了一个有用的功能:显示在home.html中选择的图像的缩略图。在本教程中,你将学习如何更新home.html,以显示用户选择的图像的缩略图。

准备工作

确保你已经按照第七章《部署和模型维护》中的部署一个使用图像数据集训练的 fastai 模型教程的步骤完成了图像分类模型的部署。

如何做…

在本教程中,你将更新图像分类模型部署中的home.html,以便显示你选择的图像的缩略图。

为了进行这些更新,请按照以下步骤操作:

  1. 将图像分类部署的目录deploy_image设置为当前目录。

  2. 通过运行以下命令,将templates子目录设置为当前目录:

    cd templates
    
  3. 在编辑器中打开home.html。我喜欢使用 Notepad++(见notepad-plus-plus.org/),但你也可以使用你喜欢的编辑器。

  4. 更新文件对话框的控件,指定一个onchange动作来调用getFile() JavaScript 函数。更新后,文件对话框的控件应如下所示:

      <input type="file" 
           id="image_field" name="image_field"
           accept="image/png, image/jpeg"
         onchange="getFile();">
    
  5. home.html中定义一个新的 JavaScript 函数getFile()

      <script>
      function getFile() {
        file_list = document.getElementById("image_field").files;
        img_f = document.createElement("img");
        img_f.setAttribute("id","displayImage");
        img_f.setAttribute("style","width:50px");
        img_f.setAttribute("alt","image to display here");
        document.body.appendChild(img_f);
        document.getElementById("displayImage").src = \
    URL.createObjectURL(file_list[0]);
      }
      </script>
    

    以下是该函数定义中的关键内容:

    a) file_list = document.getElementById("image_field").files; – 指定file_list包含与image_field文件选择器相关联的文件列表

    b) img_f = document.createElement("img"); – 在页面上定义一个新的图像元素img_f

    c) img_f.setAttribute("id","displayImage"); – 将displayImage ID 分配给新的图像元素

    d) document.body.appendChild(img_f); – 将新的图像元素添加到页面底部

    e) document.getElementById("displayImage").src = URL.createObjectURL(file_list[0]); – 指定在文件对话框中选择的文件会显示在新的图片元素中

  6. 将你的更改保存到home.html,并通过运行以下命令将deploy_image设置为当前目录:

    cd ..
    
  7. 通过运行以下命令启动 Flask 服务器:

    python web_flask_deploy_image_model.py
    
  8. 在浏览器中打开localhost:5000来显示home.html

  9. 选择test_images目录。如果一切正常,你应该会看到页面底部显示你选择的图片的缩略图,如图 8.42所示:

图 8.42 – home.html 显示选中图片的缩略图

图 8.42 – home.html 显示选中图片的缩略图

恭喜你!你已经更新了图像分类模型的部署,现在选择一张图片时会显示该图片的缩略图。

它是如何工作的……

在这个示例中,你看到了一个在 HTML 页面中动态创建元素的小例子。与home.html中的其他所有元素不同,当home.html首次加载时,显示缩略图的图片元素并不存在。图片元素只有在选择了图片文件并且调用getFile()函数时才会被创建。为什么我们不在文件首次加载时就像其他控件一样将图片元素硬编码在页面中呢?

如果我们将图片元素硬编码,那么当你加载home.html时,在选择图片之前,你会看到一个丑陋的缺失图片图标,原本应该显示缩略图的位置,如图 8.43所示:

图 8.43 – home.html 带有硬编码图片元素

图 8.43 – home.html 带有硬编码图片元素

通过在选择图片之后才动态创建图片元素,我们可以避免出现丑陋的缺失图片图标。

你可能还记得在第七章**,部署和模型维护维护你的 fastai 模型配方中,我提到过在专业的部署中,当数据集模式出现类别列的新值或全新的列时,你不需要手动调整home.html中的控件。你可以使用本配方中描述的技术,动态创建控件,来应对home.html中大部分控件的变化,使得部署更容易适应模式变动。

测试你的知识

在本章中,我们回顾了从充分利用 fastai 提供的有关模型的信息,到将你的网页部署提供给系统外部用户的一系列主题。在本节中,你将有机会练习在本章中学到的一些概念。

探索可重复结果的价值

使用回调最大化训练周期的效益的食谱中,你在训练每个模型之前调用了set_seed()函数。在那个食谱中,我提到这些调用是必要的,以确保多个训练周期的结果是可重复的。通过执行以下步骤亲自测试这一断言:

  1. 首先,复制training_with_tabular_datasets_callbacks.ipynb笔记本。

  2. 通过注释掉第一次对set_seed()的调用并重新运行整个笔记本,更新你的新笔记本。在没有回调的模型和具有早期停止回调的模型之间,fit_one_cycle()的输出有什么不同?在有早期停止回调和同时具有早期停止以及模型保存回调的模型之间,fit_one_cycle()的输出有什么不同?

  3. 通过注释掉第二次对set_seed()的调用并重新运行整个笔记本,更新你的新笔记本。现在,在没有回调的模型和具有早期停止回调的模型之间,fit_one_cycle()的输出有什么不同?在有早期停止回调和同时具有早期停止以及模型保存回调的模型之间,fit_one_cycle()的输出有什么不同?

  4. 最后,再次更新你的笔记本,通过注释掉对set_seed()的最终调用并重新运行整个笔记本。再次比较模型的结果,看看有什么变化吗?

恭喜!希望通过执行这些步骤,你能够证明可重复结果的价值。当你比较不同的模型变体,并且希望确保你在进行变体间的公平比较时,使用 fastai 中的工具来控制模型的随机初始化非常有价值,这样你就能确保多次训练运行中结果的一致性。

在你的图像分类模型部署中显示多个缩略图

展示缩略图在你的图像分类模型部署的食谱中,你学会了如何通过展示选定用于分类的图像的缩略图来增强图像分类部署,这些内容来自第七章 部署与模型维护食谱。你可能还记得,在第七章 部署与模型维护测试你的知识部分,你进行了一个练习,通过允许用户选择多个图像进行分类来增强图像分类模型部署。如果你想将这两个增强功能结合起来,既允许用户选择多个图像进行分类,又展示每个选定图像的缩略图,该怎么做呢?请按照以下步骤来了解如何实现:

  1. 确保你已经完成了第七章**“部署与模型维护”部分中的测试你的知识章节,以创建一个允许用户选择多个图像进行分类的图像分类模型部署。你将更新你在该部分完成的代码,使其显示所选图像的缩略图。

  2. 复制deploy_image_test目录,创建名为deploy_image_multi_test的副本,该目录包含你创建的多图像分类部署。为此,首先将包含deploy_image_test的目录设为当前目录,并运行以下命令:

    cp -r deploy_image_test deploy_image_multi_test
    
  3. 使deploy_image_multi_test/templates成为当前目录。你将对该目录中的home.html文件进行更新。

  4. home.html中,更新文件对话框控件,指定一个onchange动作来调用getFile() JavaScript 函数。更新后,文件对话框控件应如下所示:

    <input type="file" multiple
           id="image_field" name="image_field"
           accept="image/png, image/jpeg"
         onchange="getFile();">
    
  5. home.html中定义一个新的 JavaScript 函数,命名为getFile()。该函数将是你在显示图像分类模型部署中的缩略图配方中定义的getFile()函数的泛化:

    function getFile() {
      img_f = [];
      var i = 0;
      var di_string = "displayImage"
      file_list = \
    document.getElementById("image_field").files;
      for (file_item of file_list) {
        img_f[i] = document.createElement("img");
        var di_1 = di_string.concat(i)
        img_f[i].setAttribute("id",di_1);
        img_f[i].setAttribute("style","width:50px");
        img_f[i].setAttribute("alt","image to display here");
        document.body.appendChild(img_f[i]);
        document.getElementById(di_1).src =\
    URL.createObjectURL(file_item);
        i =  i+1
      }
      }
    

    这是该函数定义中的关键项目:

    a) file_list = document.getElementById("image_field").files; – 指定file_list包含与image_field文件选择器关联的文件列表。

    b) var di_string = "displayImage" – 定义di_string字符串,该字符串将用作将添加到页面的图像元素 ID 的前缀。

    c) for (file_item of file_list) – 指定for循环遍历file_list中的项。对于file_list中的每个项,将创建一个图像元素以显示与该项关联的图像。

    d) img_f[i] = document.createElement("img"); – 在页面上定义一个新的img_f[i]图像元素。

    e) var di_1 = di_string.concat(i) – 使用dl_string前缀和i索引定义一个di_1字符串。例如,在循环的第一次执行中,di_1的值将是displayImage1

    f) img_f[i].setAttribute("id",di_1); – 将di_1 ID 分配给img_f[i]图像元素。

    g) document.body.appendChild(img_f[i]); – 将img_f[i]图像元素添加到页面的底部。

    h) document.getElementById(di_1).src = URL.createObjectURL(file_item); – 指定与file_item关联的图像文件将显示在img_f[i]图像元素中。

    通过这些更改,用户在文件对话框中选择的文件的缩略图将显示在home.html的底部。

  6. 现在测试一切是否正常。保存对home.html的更改,将deploy_image_multi_test设为当前目录,并通过运行以下命令启动 Flask 服务器:

    python web_flask_deploy_image_model.py
    
  7. 在浏览器中打开localhost:5000以显示home.html

  8. 选择test_images目录。如果一切正常,你应该在页面底部看到你选择的每个图像的缩略图,如图 8.44所示:

图 8.44 – home.html 显示多个选定图像的缩略图

图 8.44 – home.html 显示多个选定图像的缩略图

恭喜你!你已经结合了图像分类部署的两个增强功能,允许用户选择多个图像供模型分类,并查看他们所选图像的缩略图。

结论和 fastai 的附加资源

在本书中,你已经探索了 fastai 框架的广泛功能。通过调整书中的示例,你应该能够应用 fastai 创建深度学习模型,对各种数据集进行预测。你还将能够将你的模型部署到简单的 Web 应用程序中。

fastai 有许多超出本书内容的功能。以下是一些你可以使用的额外 fastai 资源,帮助你更深入了解该平台:

感谢你花时间阅读本书并遵循书中的示例。我希望你觉得本书有用,并鼓励你应用所学的知识,利用 fastai 的深度学习力量做出伟大的事情。

posted @ 2025-07-12 11:41  绝不原创的飞龙  阅读(2)  评论(0)    收藏  举报