TensorFlow.js 学习手册(四)
译者:飞龙
第七章:模型制作资源
“通过寻找和失误我们学习。”
—约翰·沃尔夫冈·冯·歌德
你不仅限于来自 TensorFlow Hub 的模型。每天都有新的令人兴奋的模型被推文、发表和在社区中受到关注。这些模型和想法在谷歌认可的中心之外分享,有时甚至超出了 TensorFlow.js 的范围。
你开始超越园墙,与野外的模型和数据一起工作。这一章专门旨在为你提供制作现有模型的新方法,并让你面对收集和理解数据的挑战。
我们将:
-
介绍模型转换
-
介绍 Teachable Machine
-
训练一个计算机视觉模型
-
回顾训练数据的来源
-
涵盖一些关键的训练概念
当你完成这一章时,你将掌握几种制作模型的方法,并更好地理解使用数据制作机器学习解决方案的过程。
网络外模型购物
TensorFlow.js 并没有存在很长时间。因此,可用的模型数量有限,或者至少比其他框架少。这并不意味着你没有机会。你通常可以将在其他框架上训练过的模型转换为 TensorFlow.js。将现有模型转换为在新环境中工作的新模型是一种发现最近开发的资源并创建令人兴奋和现代的模型的好方法。
模型动物园
从机器学习世界中出现的一个有点可爱的术语是,有时将一组模型称为动物园。这些模型动物园是各种给定框架的任务的模型宝库,就像 TensorFlow Hub 一样。
模型动物园是一个绝佳的地方,可以找到独特的模型,这些模型可能会激发或满足你的需求。动物园经常链接到已发表的作品,解释了为模型架构和用于创建它们的数据所做的选择。
真正的好处来自于这样一个原则,一旦你学会了如何将这些模型转换为 TensorFlow.js,你可能会转换很多模型。
值得花一点时间回顾转换模型,这样你就能理解每个模型动物园或已发表模型对 TensorFlow.js 可能有多容易访问。
转换模型
许多用 Python 编程的 TensorFlow 模型以一种称为 Keras HDF5 的格式保存。HDF5 代表分层数据格式 v5,但通常被称为 Keras 或仅为 h5 文件。这种文件格式是一个带有 h5 扩展名的文件。Keras 文件格式中包含大量数据:
-
指定模型层的架构
-
一组权重值,类似于 bin 文件
-
模型的优化器和损失指标
这是更受欢迎的模型格式之一,更重要的是,即使它们是用 Python 训练的,它们也很容易转换为 TensorFlow.js。
注意
有了能够转换 TensorFlow Keras 模型的知识,这意味着你找到的任何 TensorFlow 教程都可以作为一个教程来阅读,最终产品很可能可以在 TensorFlow.js 中使用。
运行转换命令
要从 h5 转换为 TensorFlow.js model.json和 bin 文件,你需要tfjs-converter
。tfjs-converter
还可以转换除 HDF5 之外的其他 TensorFlow 模型类型,因此它是处理任何 TensorFlow 到 TensorFlow.js 格式的绝佳工具。
转换器要求你的计算机已设置 Python。使用pip
安装转换器。pip
命令是 Python 的软件包安装程序,类似于 JavaScript 中的npm
。如果你的计算机还没有准备好,有大量关于安装 Python 和pip
的教程。安装了pip
和 Python 后,你可以运行tfjs-converter
。
这是转换器的安装命令:
$ pip install tensorflowjs[wizard]
这将安装两个东西:一个无废话的转换器,您可以在自动化中使用(tensorflowjs_converter
),以及一个通过键入tensorflowjs_wizard
来运行的向导转换器。对于我们的目的,我建议使用向导界面进行转换,这样您可以利用新功能。
您可以通过在命令行中调用您新安装的tensorflowjs_wizard
命令来运行向导,然后您将被提示类似于您在图 7-1 中看到的问题。
图 7-1. 向导开始询问问题
这个向导将询问您的输入模型格式和所需的输出模型格式。根据您的答案,它还会询问一些问题。虽然向导将继续更新,但在选择所需设置时,这里有一些概念您应该牢记:
在图/层模型之间进行选择
请记住,图模型更快,但缺少一些内省和自定义属性,这些属性由层模型提供。
压缩(通过量化)
这将使您的模型从存储 32 位精度权重降至 16 位甚至 8 位精度权重值。使用更少的位数意味着您的模型在可能牺牲精度的情况下更小。量化后,您应该重新测试您的模型。大多数情况下,这种压缩对于客户端模型是值得的。
分片大小
建议的分片大小是为了优化您的模型以用于客户端浏览器缓存。除非您不在客户端浏览器中使用该模型,否则应保持推荐的大小。
注意
量化仅影响磁盘上的模型大小。这为网站提供了显著的网络传输优势,但当模型加载到 RAM 中时,值将返回到当前 TensorFlow.js 中的 32 位变量。
功能将继续出现在向导界面中。如果出现了一个让您困惑的新功能,请记住,转换模型的文档将在tfjs-converter README 源代码中提供。您的体验将类似于图 7-2。
图 7-2. Windows 上的示例向导演练
生成的文件夹包含一个转换后的 TensorFlow.js 模型,准备就绪。h5 文件现在是model.json,而可缓存的 bin 文件以块形式存在。您可以在图 7-3 中看到转换结果。
图 7-3. TensorFlow.js 模型结果
中间模型
如果您找到一个想要转换为 TensorFlow.js 的模型,现在可以检查是否有转换器将该模型移动到 Keras HDF5 格式,然后您就可以将其转换为 TensorFlow.js。值得注意的是,目前正在大力努力将模型转换为和从一个称为开放神经网络交换(ONNX)的格式标准化。目前,微软和许多其他合作伙伴正在努力正确地转换模型进出 ONNX 格式,这将允许一个独立于框架的模型格式。
如果您找到了一个已发布的模型,想要在 TensorFlow.js 中使用,但它并不是在 TensorFlow 中训练的,不要放弃希望。您应该检查该模型类型是否有 ONNX 支持。
有些模型无法直接转换为 TensorFlow,因此您可能需要通过其他转换服务进行更多的迂回路线。除了 TensorFlow 之外,另一个受欢迎的框架库是 PyTorch,大多数机器学习爱好者使用。虽然 ONNX 每天都在变得更接近,但目前从 PyTorch 转换为 TensorFlow.js 的最佳方法是通过一系列工具进行转换,如图 7-4 所示。
图 7-4. 转换模型
虽然对模型进行转换可能看起来是一项艰巨的工作,但将现有格式的模型转换为 TensorFlow.js 可以节省您几天甚至几周的时间,而不必重新创建和重新训练模型以适应已发布的数据。
您的第一个定制模型
如果只需下载现有模型,那么您就完成了。但我们不能等待谷歌发布能够分类我们所需内容的模型。您可能有一个需要 AI 对糕点有深入了解的想法。即使是谷歌的 Inception v3,如果您需要区分单个领域中各种物品之间的区别,可能也不够强大。
幸运的是,有一个技巧可以让我们利用现有模型的成果。一些模型可以稍作调整,以分类新事物!我们不需要重新训练整个模型,只需训练最后几层以寻找不同的特征。这使我们能够将像 Inception 或 MobileNet 这样的高级模型转变为识别我们想要的东西的模型。作为一个额外的好处,这种方法允许我们用极少量的数据重新训练模型。这被称为迁移学习,是在新类别上(重新)训练模型的最常见方法之一。
我们将在第十一章中介绍迁移学习的代码,但现在您也可以体验它。谷歌为人们尝试训练模型构建了一个完整的迁移学习 UI。
见见 Teachable Machine
首先,您将使用谷歌提供的一个名为 Teachable Machine 的工具。这个工具是由 TensorFlow.js 提供支持的简单网站,它允许您上传图像、上传音频,甚至使用网络摄像头进行训练、捕获数据和创建 TensorFlow.js 模型。这些模型直接在您的浏览器中进行训练,然后为您托管,以便您立即尝试您的代码。您得到的模型是 MobileNet、PoseNet 或其他实用模型的迁移学习版本,适合您的需求。由于它使用迁移学习,您根本不需要太多数据。
警告
使用少量数据创建的模型似乎效果神奇,但存在显著的偏见。这意味着它们在训练时表现良好,但在背景、光线或位置变化时会出错。
训练模型的网站位于teachablemachine.withgoogle.com。访问该网站后,您可以开始各种项目,如音频、图像,甚至身体姿势。虽然您可以并且应该尝试每一个,但本书将介绍图像项目选项。这是图 7-5 中显示的第一个选项。
图 7-5. 令人惊叹的 Teachable Machine 选项
在生成的页面上,您可以选择上传或使用网络摄像头收集每个类别的样本图像。
以下是一些您可以使用的想法,用于创建您的第一个分类器:
-
竖起大拇指还是竖起食指?
-
我在喝水吗?
-
这是哪只猫?
-
秘密手势解锁某物?
-
书还是香蕉!?
发挥您的创造力!您创建的任何模型都可以轻松展示给朋友和社交媒体,或者可以转变为一个帮助您的网页。例如,“我在喝水吗?”分类器可以连接到一个计时器,用于您的自我补水项目。只要您用一些样本训练模型,您可以想出各种有趣的项目。
就我个人而言,我将训练一个“爸爸在工作吗?”分类器。你们中的许多人可能在远程工作环境中遇到了家庭问题。如果我坐在桌子前,门是关着的,你会认为这会告诉别人我在工作,对吧?但如果门是开着的,“请进!”我会让 Teachable Machine 使用我的网络摄像头来分类我在工作时的样子和我不工作时的样子。
很酷的一点是,由于检测器将与网站绑定,“爸爸在工作吗?”可以扩展到做各种令人惊叹的事情。它可以发送短信,打开“不可用”灯,甚至告诉我的亚马逊 Echo 设备在被问及我是否在工作时回答“是”。只要我能制作一个快速可靠的 AI 图像分类器,就有无限的机会。
从头开始训练是一个可扩展的解决方案,但目前的任务是训练我在办公室的存在,为此我们将使用 Teachable Machine。
使用 Teachable Machine
让我们快速浏览一下使用 Teachable Machine 创建模型的用户界面。用户界面设置为网络图,信息从左到右自上而下填写。使用该网站很容易。跟随我们一起查看图 7-6。
图 7-6。图像项目用户界面导览
-
这个上部标题应该很小,在较大的监视器上不会妨碍。从标题中,您可以使用 Google Drive 来管理您的数据和结果,这样您就可以从上次离开的地方继续或与他人分享您的模型训练。
-
顶部项目称为“类别 1”,表示您分类的一个类别。当然,您可以重命名它!我已将我的重命名为“工作”。在这个工作流卡中,您可以提供访问您的网络摄像头或上传符合此分类的图像文件。
-
这个第二个工作流卡是任何第二类。在我的示例中,这可能是“免费”或“不工作”。在这里,您提供适合您的次要分类的数据。
-
所有类别都进入训练工作流程。当您有要构建的示例时,您可以点击“训练模型”按钮并积极训练模型。当我们到达高级选项卡时,我们将深入了解这是如何进行的。
-
预览部分立即显示模型实时分类的结果。
收集数据和训练
您可以按住网络摄像头的“按住录制”按钮,立即提供数百张示例数据的图像。在您的数据集中尽可能评估和包含变化是至关重要的。例如,如果您正在做“竖起大拇指还是竖下大拇指”,重要的是您在屏幕周围移动手部,捕捉不同角度,并将手放在面部、衬衫和任何其他复杂背景前。
对我来说,我调整了我的照明,因为有时我有一个摄像头主灯,有时我有背光。几秒钟内,我有了数百种不同条件的办公室门开着和关着的照片。我甚至拍了一些照片,我的门是关着的,但我没有坐在桌子前。
Teachable Machine 的一个很棒的地方是它可以在浏览器中快速给出结果,因此如果模型需要更多数据,您可以随时回来并立即添加更多数据。
一旦您有几百张照片,您可以点击“训练模型”按钮,您将看到一个“训练…”进度图(参见图 7-7)。
图 7-7。Teachable Machine 活动训练
那么现在发生了什么?简而言之,Teachable Machine 正在使用您的图像执行迁移学习来重新训练 MobileNet 模型。您的数据中随机选择了 85%用于训练模型,另外 15%用于测试模型的性能。
点击高级选项卡查看此特定配置的详细信息。这将暴露出通常称为超参数的机器学习训练的一些内容(参见图 7-8)。这些超参数是可调整的模型训练参数。
图 7-8。Teachable Machine 超参数
在这里,您将看到一些新术语。虽然现在学习这些术语并不是必要的,但您最终需要学习它们,因此我们将快速介绍它们。当您开始编写自己的模型时,每个概念都会出现在第八章中。
纪元
如果您来自编码背景,尤其是 JavaScript 编码,那么纪元是 1970 年 1 月 1 日。这 不是 在这个领域中纪元的含义。在机器学习训练中,一个纪元是对训练数据的完整遍历。在一个纪元结束时,AI 至少已经看到了所有的训练数据一次。五十个纪元意味着模型将不得不看到数据 50 次。一个很好的类比是单词卡。这个数字表示您与模型一起浏览整个单词卡组的次数,以便它学习。
批量大小
模型是以加载到内存中的批次进行训练的。有了几百张照片,您可以轻松处理所有图像,但最好按合理的增量进行批处理。
学习率
学习率影响机器学习模型在每次预测时应该如何调整。您可能会认为更高的学习率总是更好,但您会错。有时,特别是在微调迁移学习模型时,关键在于细节(如第十一章中所述)。
卡片底部还有一个带有“Under the hood”文本的按钮,点击它将为您提供有关训练模型进度的详细信息。随时查看报告。您将在以后实施这些指标。
验证模型
一旦 Teachable Machine 完成,它会立即将模型连接到您的网络摄像头,并显示模型的预测结果。这是一个很好的机会让您测试模型的结果。
对我来说,当我坐在办公桌前,门是关着的时候,模型预测我正在工作。万岁!我有一个可用的模型准备好了。两个类的表现都非常出色,如图 7-9 所示。
图 7-9。模型运行
理想情况下,您的训练进展顺利。现在,检索训练好的模型是至关重要的,这样它就可以在您更广泛的项目中实施。如果您想与朋友分享您的模型,可以在预览中点击“导出模型”按钮,您将获得各种选项。新的模态窗口提供了在 TensorFlow、TensorFlow Lite 和 TensorFlow.js 中应用您的模型的路径。甚至还有一个选项可以免费托管您的训练模型,而不是自己下载和托管模型。我们提供了所有这些友好的选项以及一些巧妙的复制粘贴代码,让您快速实施这些模型。导出代码屏幕应该类似于图 7-10。
图 7-10。Teachable Machine 导出选项
当您的模型被下载或发布时,您的数据不会随之发布。要保存数据集,您需要将项目保存在 Google Drive 中。如果您计划随着时间推进模型或扩大数据集,请记住这一点。识别和处理边缘情况是数据科学过程的一部分。
在 Teachable Machine 的复制粘贴部分提供的免费代码隐藏了名为@teachablemachine/image的 NPM 包中的网络摄像头和张量的细节。虽然这对于不了解网络摄像头和张量的人来说很好,但对于最终产品来说却毫无用处。您从第六章中获得的高级 UI 技能使您的创造潜力远远超过了复制粘贴代码选项。
提示
每个 Teachable Machine 模型都是不同的;您刚刚训练的视觉模型是建立在我们的老朋友 MobileNet 分类器之上的。因此,当您实施模型时,将输入调整为 224 x 224。
你刚刚训练了你的第一个模型。然而,我们尽可能地省略了很多步骤。使用用户界面训练模型将成为机器学习的一个重要部分,它可以帮助每个新手获得一个出色的开始。但像你这样的张量巫师可以训练一个更加动态的模型。你显然希望通过编写一些 JavaScript 命令你的机器。所以让我们开始通过编写一些 JavaScript 来训练一个模型。
机器学习陷阱
在编码时,任何开发人员可能会面临各种问题。尽管编程语言各不相同,但有一组核心的陷阱会延续到每个基础设施。机器学习也不例外。虽然可能会有特定于任何选择的类型和问题的问题,但早期识别这些问题很重要,这样你就可以发现数据驱动算法中最常见的一些复杂问题。
我们现在将快速阐述一些概念,但每个概念在本书的其余部分涉及到工作时都会重新讨论:
-
少量数据
-
糟糕的数据
-
数据偏差
-
过拟合
-
欠拟合
让我们回顾一下这些,这样我们就可以在接下来的章节中留意它们。
少量数据
有人给我提出了一个关于机器学习解决方案的绝妙想法,他们有三个标记样本。这个世界上很少有东西能从这么小的训练集中受益。当数据是你训练算法的方式时,你需要相当数量的数据。有多少?没有一个适用于每个问题的答案,但你应该倾向于更多的数据而不是更少。
糟糕的数据
有些人的生活干净、有序,但在现实世界中,数据不会无意中变得如此。如果你的数据缺失、标记错误,或者完全不合理,它可能会在训练中造成问题。很多时候,数据需要被清理,异常值需要被移除。准备好数据只是一个重要且关键的步骤。
数据偏差
你的数据可能被清晰地标记,每个细节都在正确的位置,但它可能缺少使其在实际情况下工作的信息。在某些情况下,这可能会引起严重的道德问题,而在其他情况下,这可能会导致你的模型在各种条件下表现不佳。例如,我之前训练的“爸爸在工作吗?”模型(图 7-9)可能不适用于其他人的办公室配置,因为数据只针对我的办公室。
过拟合
有时模型被训练到只在训练集数据上表现良好。在某些情况下,一个更直接但得分较低的准确性可能更好地适应新数据点。
看看这个分离图在图 7-11 中是如何过拟合数据的?虽然它完美地解决了给定的问题,但当添加新的从未见过的点时,它可能会变得更慢并失败。
图 7-11. 过拟合数据
有时你会听到过拟合被称为高方差,这意味着你在训练数据中的波动会导致模型在新数据上随机失败。
如果你的目标是让你的模型在新的、以前从未见过的数据上工作,过拟合可能是一个真正的问题。幸运的是,我们有测试和验证集来帮助。
欠拟合
如果你的模型没有被充分训练,或者它的结构无法适应数据,解决方案可能会失败,甚至完全偏离任何外推或额外数据。这是过拟合的反面,但在同样的意义上,它会产生一个糟糕的模型。
看看图 7-12 中的分离图是如何欠拟合数据的?
图 7-12. 欠拟合数据
当模型欠拟合时,就说模型具有高偏差,因为对数据的基本假设实际上是错误的。虽然类似,但不要将这个术语与之前讨论的数据偏差混淆。
数据集购物
现在你明白为什么拥有多样化的数据是至关重要的。虽然 Teachable Machine 的“爸爸在工作吗?”模型对我很有用,但远远不够多样化,无法用于其他办公室。令人高兴的是,机器学习社区最令人印象深刻的一点是大家都愿意分享他们辛苦获得的数据集。
在收集数据之前,研究一下其他人是否已经发布了可用的标记数据是很有帮助的。了解专家机器学习数据集是如何组织的也是有益的。
数据集就像 JavaScript 库:一开始可能看起来很独特,但过一段时间后,你会发现同样的数据集一再被引用。世界各地的大学都有出色的有用数据集目录,甚至谷歌也有一个类似 TensorFlow Hub 的数据集托管服务,但没有一个能与 Kaggle 这个数据集居所相提并论。
Kaggle拥有大量各种类型的数据集。从鸟鸣到 IMDb 评论,你可以使用 Kaggle 提供的各种数据训练各种模型。图 7-13 展示了一个友好且可搜索的数据集界面。
图 7-13。Kaggle 提供了超过 60,000 个免费数据集
无论你是在研究用于训练模型的数据,还是在寻找使用机器学习制作新奇事物的想法,Kaggle 都能满足你的需求。
注意
Kaggle 不仅提供数据集,还是一个分享、竞争和赢取奖品的社区。
如果你对 Kaggle 的课外活动不感兴趣,你通常可以使用谷歌的数据集搜索网站,很可能会找到你的 Kaggle 数据集和其他数据集:https://datasetsearch.research.google.com。
流行数据集
虽然数据集的列表每天都在增长,但很长一段时间内可供选择的数据集并不多。已发布的数据集很少,因此一些数据集成为训练示例的基础。其他数据集作为其类别的第一个发布,并不知不觉地成为某种机器学习的品牌大使。就像秘密口令一样,这些流行数据集在演讲和文档中被随意使用。了解一些最常见和著名的数据集是很有益的:
ImageNet 被用来训练一些最流行的计算机视觉模型。这个大型图像数据集一直被学术研究人员用来评估模型。
这是一个 28 x 28 灰度手写数字的集合,用于训练一个读取数字的模型。它通常是计算机视觉模型的“Hello World”。其名称来源于其来源,即来自国家标准与技术研究所的修改数据集。
1936 年,罗纳德·费舍尔发现可以通过三个物理测量来识别鸢尾花的属种。这个数据集是非视觉分类的经典。
这个数据集包含了中位数房屋价值及其相关属性,用于解决最佳拟合线(线性回归)模型。
这是“不沉”RMS 泰坦尼克号于 1912 年 4 月 15 日沉没的乘客日志。我们将使用这个数据集在第九章中创建一个模型。
对于酿酒师和手工艺者来说,利用机器学习来识别什么使一种美味饮料是令人振奋的。这个数据集包含每种葡萄酒的物理化学性质及其评分。
有很多健康护理数据集可用。这是一个基于患者病史的小型且易接近的糖尿病数据集。
虽然 ImageNet 是一个金标准,但有点难以接近和复杂。CIFAR 数据集是一个低分辨率且友好的图像集合,用于分类。
这是来自 Amazon.com 多年来的产品评论集合。该数据集已被用来训练文本的情感倾向,因为你有用户的评论和他们的评分。与此相近的是 IMDb 评论数据集。
这是一个大规模的目标检测、分割和字幕数据集。
这 10 个是标准参考数据集的很好起点。机器学习爱好者会在推特、演讲和博客文章中随意引用这些数据集。
章节复习
当然,你没有金星火山的各种照片。你怎么可能有呢?这并不意味着你不能拿一个为此训练过的模型,移植到你的新浏览器游戏中。只需从 Kaggle 下载数据集并上传图片到 Teachable Machine,创建一个体面的“火山还是非火山”天文模型。就像 TensorFlow.js 将你带入机器学习轨道一样,这些现有模型和数据集为你的应用程序掌握打下了基础。
像 Web 开发一样,机器学习包含各种专业化。机器学习依赖于数据、模型、训练和张量等多种技能。
章节挑战:再见 MNIST
现在轮到你将一个模型从 Keras HDF5 转换到 TensorFlow.js 了。在与本书相关的代码中,你会找到一个mnist.h5文件,其中包含了用于识别手写数字的模型。
-
创建一个图形 TensorFlow.js 模型。
-
用
uint8
量化模型使其变小。 -
使用通配符访问模型中的所有权重。
-
将分片大小设置为 12,000。
-
保存到一个文件夹./minist(min因为它被量化了,明白吗!)。
回答以下问题:
-
生成了多少个二进制文件和组?
-
最终输出大小是多少?
-
如果你使用默认的分片大小,会生成多少个二进制文件?
您可以在附录 B 中找到这个挑战的答案。
复习问题
让我们回顾一下你在本章编写的代码中学到的教训。花点时间回答以下问题:
-
如果你为特定任务提供了大量数据,训练之前你会有哪些担忧和想法?
-
如果一个模型经过训练,准确率达到了 99%,但是当你在现场使用它时,表现很糟糕,你会说发生了什么?
-
谷歌创建的帮助您训练自己模型的网站的名称是什么?
-
使用谷歌网站的缺点是什么?
-
用来训练 MobileNet 和其他流行的机器学习模型的图像数据集是什么?
这些练习的解决方案可以在附录 A 中找到。
第八章:训练模型
“不要求负担更轻,而要求更宽广的肩膀。”
—犹太谚语
尽管令人印象深刻的模型和数据的供应将继续增长并溢出,但你可能希望做的不仅仅是消费 TensorFlow.js 模型。你将想出以前从未做过的想法,那天不会有现成的选择。现在是时候训练你自己的模型了。
是的,这是世界上最优秀的头脑竞争的任务。虽然关于训练模型的数学、策略和方法论可以写成一本书,但核心理解将至关重要。你必须熟悉使用 TensorFlow.js 训练模型的基本概念和好处,以充分利用这个框架。
我们将:
-
用 JavaScript 代码训练你的第一个模型
-
提升对模型架构的理解
-
回顾如何在训练过程中跟踪状态
-
涵盖一些训练的基本概念
当你完成这一章时,你将掌握几种训练模型的方法,并更好地理解使用数据制定机器学习解决方案的过程。
训练 101
现在是时候揭开魔法,用 JavaScript 训练一个模型了。虽然 Teachable Machine 是一个很好的工具,但它有限。要真正赋予机器学习力量,你需要确定你想要解决的问题,然后教会机器找到解决方案的模式。为了做到这一点,我们将通过数据的眼睛看问题。
在写任何代码之前,看看这个信息的例子,看看你能否确定这些数字之间的相关性。你有一个函数f
,它接受一个数字并返回一个数字。以下是数据:
-
给定-1,结果为-4。
-
给定 0,结果为-2。
-
给定 1,结果为 0。
-
给定 2,结果为 2。
-
给定 3,结果为 4。
-
给定 4,结果为 6。
你能确定 5 的答案是什么吗?你能推断出 10 的解决方案吗?在继续之前花点时间评估数据。你们中的一些人可能已经找到了解决方案:答案 = 2x - 2。
函数f
是一条简单的线,如图 8-1 所示。知道这一点后,你可以快速解出输入为 10 时的结果为 18。
图 8-1。X = 10 意味着 Y = 18
从给定数据解决这个问题正是机器学习可以做到的。让我们准备并训练一个 TensorFlow.js 模型来解决这个简单的问题。
要应用监督学习,你需要做以下事情:
-
收集你的数据(输入和期望解决方案)。
-
创建和设计模型架构。
-
确定模型应该如何学习和衡量错误。
-
训练模型并确定训练时间。
数据准备
为了准备一台机器,你将编写代码来提供输入张量,即值[-1, 0, 1, 2, 3, 4]
及其对应的答案[-4, -2, 0, 2, 4, 6]
。问题的索引必须与预期答案的索引匹配,这在思考时是有意义的。因为我们给模型所有值的答案,这就是为什么这是一个监督学习问题。
在这种情况下,训练集有六个例子。机器学习很少会用在这么少的数据上,但问题相对较小且简单。正如你所看到的,没有任何训练数据被保留用于测试模型。幸运的是,你可以尝试这个模型,因为你知道最初用来创建数据的公式。如果你对训练和测试数据集的定义不熟悉,请查看第一章中的“常见 AI/ML 术语”。
设计模型
设计模型的想法可能听起来很繁琐,但诚实的答案是,这是理论、试验和错误的混合。在设计师了解架构的性能之前,模型可能需要经过数小时甚至数周的训练。整个研究领域可能致力于模型设计。您将为本书创建的 Layers 模型将为您提供良好的基础。
设计模型的最简单方法是使用 TensorFlow.js 的 Layers API,这是一个高级 API,允许您按顺序定义每个层。实际上,要启动您的模型,您将从代码tf.sequential();
开始。您可能会听到这被称为“Keras API”,因为这种模型定义风格的起源。
您将创建的模型来解决您正在尝试解决的简单问题将只有一个层和一个神经元。当您考虑到一条线的公式时,这是有道理的;这不是一个非常复杂的方程。
注意
当你熟悉密集网络的基本方程时,就会惊讶地发现为什么在这种情况下单个神经元会起作用,因为一条线的公式是 y = mx + b,而人工神经元的公式是 y = Wx + b。
要向模型添加一层,您将使用model.add
,然后定义您的层。使用 Layers API,每个添加的层都会自行定义并根据model.add
调用的顺序自动连接,就像推送到数组一样。您将在第一层中定义模型的预期输入,并且您添加的最后一层将定义模型的输出(参见示例 8-1)。
示例 8-1。构建一个假设模型
model.add(ALayer)
model.add(BLayer)
model.add(CLayer)
// Currently, model is [ALayer, BLayer, CLayer]
示例 8-1 中的模型将有三层。ALayer
将负责识别预期的模型输入和自身。BLayer
不需要识别其输入,因为可以推断输入将是ALayer
。因此,BLayer
只需要定义自身。CLayer
将识别自身,并且因为它是最后一个,这将确定模型的输出。
让我们回到您试图编码的模型。当前问题的架构模型目标只有一个具有一个神经元的层。当您编写该单个层时,您将定义您的输入和输出。
// The entire inner workings of the model
model.add(
tf.layers.dense({
inputShape: 1, // one value 1D tensor
units: 1 // one neuron - output tensor
})
);
结果是一个简单的神经网络。在绘制图时,网络有两个节点(参见图 8-2)。
图 8-2。一个输入和一个输出
通常,层具有更多的人工神经元(图节点),但也更复杂,并具有其他要配置的属性。
识别学习指标
接下来,您需要告诉您的模型如何识别进展以及如何变得更好。这些概念并不陌生;它们在软件中只是看起来有点奇怪。
每当我试图将激光指示器对准某物时,我通常会错过。但是,我可以看到我稍微偏左或偏右,然后进行调整。机器学习也是如此。它可能会随机开始,但算法会自我纠正,并且需要知道您希望它如何做到这一点。最符合我的激光指示器示例的方法将是梯度下降。优化激光指示器的最平滑迭代方法称为随机梯度下降。这就是我们将在这种情况下使用的方法,因为它效果很好,并且对于您下次晚宴听起来相当酷。
至于测量错误,您可能认为简单的“对”和“错”会起作用,但是差几个小数点和错上千有很大的区别。因此,通常依赖损失函数来帮助您确定 AI 预测猜测的错误程度。有很多衡量错误的方法,但在这种情况下,均方误差(MSE)是一个很好的衡量标准。对于那些需要了解数学的人,MSE 是估计值(y)和实际值(带有小帽的 y)之间的平均平方差。如果您熟悉常见的数学符号,可以将其表示如下:
为什么您喜欢这个公式而不是简单的原始答案距离?MSE 中蕴含了一些数学优势,有助于将方差和偏差作为正误差分数进行整合。不深入统计学,它是解决拟合数据线的最常见损失函数之一。
提示
随机梯度下降和均方误差散发着数学起源的气息,对于一个实用的开发人员来说,这些都无法告诉他们的目的。在这种情况下,最好吸收这些术语的含义,如果您感到有冒险精神,可以观看大量视频,详细解释它们。
当您准备告诉模型使用特定的学习指标并且添加完所有层到模型后,这一切都包含在 .compile
调用中。TensorFlow.js 足够聪明,了解梯度下降和均方误差。您可以通过它们的批准字符串等效项来识别它们,而不是编写这些函数:
model.compile({
optimizer: "sgd",
loss: "meanSquaredError"
});
使用框架的一个巨大好处是,随着机器学习世界发明新的优化器如“Adagrad”和“Adamax”,只需在模型架构中简单更改一个字符串¹,就可以尝试并调用它们。将“sgd”切换为“adamax”对于开发人员来说几乎不需要时间,可能会显著提高模型训练时间,而无需阅读有关随机优化的已发表论文。
在不了解函数的具体情况下识别函数,提供了一种苦乐参半的好处,类似于更改文件类型而无需了解每种类型的完整结构。对于每种的优缺点有一点了解是很有帮助的,但您不需要记住规范。在架构时花点时间阅读可用内容是值得的。
不用担心。您会看到相同的名称一遍又一遍地使用,所以很容易掌握它们。
此时,模型已创建。如果要求它预测任何内容,它将失败,因为它没有进行任何训练。架构中的权重完全是随机的,但您可以通过调用 model.summary()
来查看层。输出直接显示在控制台上,看起来有点像 示例 8-2。
示例 8-2. 在 Layers 模型上调用 model.summary()
打印层
_________________________________________________________________
Layer (type) Output shape Param #
=================================================================
dense_Dense6 (Dense) [null,1] 2
=================================================================
Total params: 2
Trainable params: 2
Non-trainable params: 0
_________________________________________________________________
层dense_Dense6
是在 TensorFlow.js 后端中引用此层的自动 ID。您的 ID 可能会有所不同。这个模型有两个可训练参数,因为一条线是 y = mx + b,对吧?从视觉上看,这个模型的参数是可训练的。我们将在后面介绍不可训练的参数。这个单层模型已经准备就绪。
训练模型的任务
训练模型的最后一步是将输入组合到架构中,并指定训练的持续时间。如前所述,这通常以 epochs 来衡量,即模型将多少次查看带有正确答案的闪卡,然后完成后停止训练。您应该使用的 epochs 数量取决于问题的规模、模型以及“足够好”的正确性。在某些模型中,再获得半个百分点值得几小时的训练,而在我们的情况下,模型足够准确,可以在几秒内得到正确答案。
训练集是一个具有六个值的 1D 张量。如果 epochs 设置为 1,000,那么模型将有效地训练 6,000 次迭代,这将在任何现代计算机上最多花费几秒钟。将一条线拟合到点的微不足道的问题对于计算机来说非常简单。
将所有内容整合在一起
现在您已经熟悉了高级概念,您可能迫不及待地想用代码解决这个问题。以下是用数据训练模型的代码,然后立即要求模型为值10
提供答案,如前所述。
// Inputs
const xs = tf.tensor([-1, 0, 1, 2, 3, 4]); // ①
// Answers we want from inputs
const ys = tf.tensor([-4, -2, 0, 2, 4, 6]);
// Create a model
const model = tf.sequential(); // ②
model.add( // ③
tf.layers.dense({
inputShape: 1,
units: 1
})
);
model.compile({ // ④
optimizer: "sgd",
loss: "meanSquaredError"
});
// Print out the model structure
model.summary();
// Train
model.fit(xs, ys, { epochs: 300 }).then(history => { // ⑤
const inputTensor = tf.tensor([10]);
const answer = model.predict(inputTensor); // ⑥
console.log(`10 results in ${Math.round(answer.dataSync())}`);
// cleanup
tf.dispose([xs, ys, model, answer, inputTensor]); // ⑦
});
①
数据准备在具有输入和期望输出的张量中完成。
②
开始一个顺序模型。
③
添加唯一的具有一个输入和一个输出的层,如前所述。
④
使用给定的优化器和损失函数完成顺序模型。
⑤
模型被告知用fit
进行 300 个 epochs 的训练。这是一个微不足道的时间量,当fit
完成时,它返回的承诺会被解决。
⑥
要求训练模型为输入张量10
提供答案。您需要四舍五入答案以获得整数结果。
⑦
在获得答案后立即处理所有内容。
恭喜!您已经用代码从头开始训练了一个模型。您刚刚解决的问题被称为线性回归问题。它有各种用途,是预测房价、消费者行为、销售预测等的常用工具。一般来说,在现实世界中,点并不完全落在一条直线上,但现在您有能力将分散的线性数据转化为预测模型。因此,当您的数据看起来像图 8-3 时,您可以像图 8-4 中所示解决问题。
图 8-3. 分散的线性数据
图 8-4. 使用 TensorFlow.js 预测最佳拟合线
现在您已经熟悉了训练的基础知识,您可以扩展您的流程,了解解决更复杂模型所需的步骤。训练模型在很大程度上取决于架构以及数据的质量和数量。
非线性训练 101
如果每个问题都基于线性,那么就不需要机器学习了。统计学家从 19 世纪初就开始解决线性回归问题。不幸的是,一旦您的数据是非线性的,这种方法就会失败。如果您让 AI 解决 Y = X²会发生什么?
图 8-5. 简单的 Y = X²
更复杂的问题需要更复杂的模型架构。在本节中,您将学习基于层的模型的新属性和特性,以及处理数据的非线性分组。
您可以向神经网络添加更多节点,但它们仍然存在于线性函数的分组中。为了打破线性,现在是时候添加激活函数了。
激活函数类似于大脑中的神经元。是的,这个比喻又来了。当一个神经元在电化学上接收到信号时,并不总是激活。在神经元发射动作电位之前,需要一个阈值。同样地,神经网络具有一定程度的偏见和类似于开关的动作电位,当它们达到由于传入信号而引起的阈值时发生(类似于去极化电流)。简而言之,激活函数使神经网络能够进行非线性预测。²
注意
如果您知道您的解决方案需要是二次的,那么有更聪明的方法来解决二次函数。在本节中,您将为 X²解决问题,这是专门为了更多地了解 TensorFlow.js 而编排的,而不是为了解决简单的数学函数。
是的,这个练习可以很容易地在不使用 AI 的情况下解决,但那样有什么乐趣呢?
收集数据
指数函数可能返回一些非常大的数字,加快模型训练速度的一个技巧是保持数字及其之间的距离较小。您会一次又一次地看到这一点。对于我们的目的,模型的训练数据将是 0 到 10 之间的数字。
const jsxs = [];
const jsys = [];
const dataSize = 10;
const stepSize = 0.001;
for (let i = 0; i < dataSize; i = i + stepSize) {
jsxs.push(i);
jsys.push(i * i);
}
// Inputs
const xs = tf.tensor(jsxs);
// Answers we want from inputs
const ys = tf.tensor(jsys);
这段代码准备了两个张量。xs
张量是 10,000 个值的分组,ys
是这些值的平方。
向神经元添加激活
为给定层中的神经元选择激活函数以及您的模型大小本身就是一门科学。这取决于您的目标、您的数据和您的知识。就像编码一样,您可以提出几种几乎同样有效的解决方案。经验和实践将帮助您找到适合的解决方案。
在添加激活时,重要的是要注意 TensorFlow.js 中内置了许多激活函数。其中最流行的激活函数之一被称为 ReLU,代表修正线性单元。正如您可能从名称中推断出的那样,它来自科学术语的核心,而不是机智的 NPM 软件包名称。有各种文献讨论了在某些模型中使用 ReLU 相对于其他激活函数的好处。您必须知道 ReLU 是激活函数的一个流行选择,开始使用它应该没问题。随着您对模型架构的了解越来越多,您应该随意尝试其他激活函数。与许多其他选择相比,ReLU 有助于模型更快地训练。
在上一个模型中,您只有一个节点和一个输出。现在增加网络的大小变得重要。没有一个固定的大小公式可供使用,因此每个问题的第一阶段通常需要一些试验。为了我们的目的,我们将增加一个包含 20 个神经元的密集层。密集层意味着该层中的每个节点都连接到其之前和之后的每个节点。生成的模型看起来像图 8-6。
图 8-6. 神经网络架构(20 个神经元)
从左到右浏览图 8-6 中显示的架构,一个数字进入网络,20 个神经元的层被称为隐藏层,最终值输出在最后一层。隐藏层是输入和输出之间的层。这些隐藏层添加了可训练的神经元,并使模型能够处理更复杂的模式。
要添加这一层并为其提供激活函数,您将在序列中指定一个新的密集层:
model.add(
tf.layers.dense({
inputShape: 1, // ①
units: 20, // ②
activation: "relu" // ③
})
);
model.add(
tf.layers.dense({
units: 1 // ④
})
);
①
第一层将输入张量定义为一个单一数字。
②
指定层应该有 20 个节点。
③
为您的层指定一个花哨的激活函数。
④
添加最终的单单元层以获取输出值。
如果您编译模型并打印摘要,您将看到类似于示例 8-3 的输出。
示例 8-3。调用model.summary()
以获取当前结构
_________________________________________________________________
Layer (type) Output shape Param #
=================================================================
dense_Dense1 (Dense) [null,20] 40
_________________________________________________________________
dense_Dense2 (Dense) [null,1] 21
=================================================================
Total params: 61
Trainable params: 61
Non-trainable params: 0
_________________________________________________________________
此模型架构有两个与先前层创建代码匹配的层。null
部分代表批量大小,由于它可以是任何数字,因此留空。例如,第一层表示为[null,20]
,因此四个值的批次将为模型提供输入[4, 20]
。
您会注意到模型共有 61 个可调参数。如果您查看图 8-6 中的图表,您可以绘制线条和节点以获取参数。第一层有 20 个节点和 20 条线连接到它们,这就是为什么它有 40 个参数。第二层有 20 条线都连接到一个单个节点,这就是为什么只有 21 个参数。您的模型已准备好训练,但这次要大得多。
如果您进行这些更改并开始训练,您可能会听到您的 CPU/GPU 风扇启动并看到一堆无用的东西。听起来计算机可能正在训练,但肯定很好看到某种进展。
观看训练
TensorFlow.js 拥有各种令人惊奇的工具,可帮助您识别训练进度。特别是,fit
配置的一个属性称为callbacks
。在callbacks
对象内部,您可以连接到训练模型的某些生命周期,并运行任何您想要的代码。
由于您已经熟悉一个 epoch(对训练数据的完整运行),这是您在本例中将使用的时刻。这是一个简洁但有效的获取某种控制台消息的方法。
const printCallback = { // ①
onEpochEnd: (epoch, log) => { // ②
console.log(epoch, log); // ③
}
};
①
创建包含您想要连接的所有生命周期方法的回调对象。
②
onEpochEnd
是训练支持的许多已识别的生命周期回调之一。其他枚举在框架的fit
部分文档中。
③
打印审查的值。通常,您会对这些信息进行更深入的处理。
注意
通过在fit
配置中设置stepsPerEpoch
数字,可以重新定义一个 epoch。使用此变量,一个 epoch 可以成为任何数量的训练数据。默认情况下,这设置为null
,因此一个 epoch 设置为您的训练集中唯一样本的数量除以批量大小。
剩下要做的就是将您的对象传递给模型的fit
配置,同时传递您的 epochs,您应该在模型训练时看到日志。
await model.fit(xs, ys, {
epochs: 100,
callbacks: printCallback
});
onEpochEnd
回调会打印到您的控制台,显示训练正在进行。在图 8-7 中,您可以看到您的 epoch 和日志对象。
图 8-7。epochs 19 到 26 的onEpochEnd
日志
能够看到模型实际上正在训练,甚至能够知道它所处的 epoch,这真是一种清新的感觉。但是,日志值是怎么回事?
模型日志
模型通过损失函数告知如何定义损失。您希望在每个 epoch 中看到的是损失下降。损失不仅仅是“对还是错?”它是关于模型有多错,以便它可以学习。每个 epoch 之后,模型都会报告损失,在一个良好的模型架构中,这个数字会迅速下降。
您可能对查看准确性感兴趣。大多数情况下,准确性是一个很好的指标,我们可以在日志中启用准确性。但是,对于这样的模型,准确性并不是一个很好的指标。例如,如果您问模型预测[7]
的输出应该是多少,而模型回答49.0676842
而不是49
,那么它的准确性为零,因为它是错误的。虽然接近的结果在四舍五入后会有较低的损失并且准确,但从技术上讲,它是错误的,模型的准确性评分会很差。让我们在它更有效时再启用准确性。
改进训练
损失值相当高。什么是高损失值?具体而言,这取决于问题。但是,当您看到错误值为 800+时,通常可以说训练尚未完成。
Adam 优化器
幸运的是,您不必让计算机训练几周。目前,优化器设置为随机梯度下降(sgd
)的默认值。您可以修改sgd
预设,甚至选择不同的优化器。最受欢迎的优化器之一称为 Adam。如果您有兴趣尝试 Adam,您不必阅读2015 年发表的 Adam 论文;您只需将sgd
的值更改为adam
,然后您就可以开始了。这是您可以享受框架优势的地方。只需更改一个小字符串,整个模型架构就已更改。Adam 对解决某些类型的问题具有显著的好处。
更新后的编译代码如下:
model.compile({
optimizer: "adam",
loss: "meanSquaredError"
});
使用新的优化器,损失在几个时期内降至 800 以下,甚至降至 1 以下,如您在图 8-8 中所见。
图 8-8。时期 19 到 26 的onEpochEnd
日志
经过 100 个时期,模型对我来说仍在取得进展,但在损失值为0.03833026438951492
时停止。每次运行都会有所不同,但只要损失很小,模型就会正常工作。
提示
修改和调整模型架构以便为特定问题更快地训练或收敛是经验和实验的结合。
情况看起来不错,但还有一个功能我们应该添加,有时可以显著缩短训练时间。在一台相当不错的机器上,这 100 个时期大约需要 100 秒才能运行。您可以通过一行批处理数据来加快训练速度。当您将batchSize
属性分配给fit
配置时,训练速度会大大加快。尝试在 fit 调用中添加批处理大小:
await model.fit(xs, ys, {
epochs: 100,
callbacks: printCallback,
batchSize: 64 // ①
});
①
对于我的机器,64 的batchSize
将训练时间从 100 秒减少到 50 秒。
注意
批处理大小是效率和内存之间的权衡。如果批处理太大,这将限制能够运行训练的机器。
您有一个在合理时间内训练的模型,几乎没有额外的成本。但是,增加批处理大小是一个您可以并且应该审查的选项。
更多节点和层
整个时间内,模型的形状和大小一直是相同的:一个包含 20 个节点的“隐藏”层。不要忘记,您可以随时添加更多层。作为一个实验,添加另一个包含 20 个节点的层,这样您的模型架构看起来像图 8-9。
图 8-9。神经网络架构(20×20 隐藏节点)
使用 Layers 模型架构,您可以通过添加一个新层来构建这个模型。请参阅以下代码:
model.add(
tf.layers.dense({
inputShape: 1,
units: 20,
activation: "relu"
})
);
model.add(
tf.layers.dense({
units: 20,
activation: "relu"
})
);
model.add(
tf.layers.dense({
units: 1
})
);
结果模型训练速度较慢,这是有道理的,但也收敛速度更快,这也是有道理的。这个更大的模型在 20 秒的训练时间内只需 30 个时期就为输入[7]
生成了正确的值。
将所有内容放在一起,您的结果代码执行以下操作:
-
创建一个重要的数据集
-
创建几个深度连接的层,使用 ReLU 激活
-
将模型设置为使用先进的 Adam 优化
-
使用 64 块数据训练模型,并在途中打印进度
从头到尾的整个源代码如下:
const jsxs = [];((("improving training", "adding more neurons and layers")))
const jsys = [];
// Create the dataset
const dataSize = 10;
const stepSize = 0.001;
for (let i = 0; i < dataSize; i = i + stepSize) {
jsxs.push(i);
jsys.push(i * i);
}
// Inputs
const xs = tf.tensor(jsxs);
// Answers we want from inputs
const ys = tf.tensor(jsys);
// Print the progress on each epoch
const printCallback = {
onEpochEnd: (epoch, log) => {
console.log(epoch, log);
}
};
// Create the model
const model = tf.sequential();
model.add(
tf.layers.dense({
inputShape: 1,
units: 20,
activation: "relu"
})
);
model.add(
tf.layers.dense({
units: 20,
activation: "relu"
})
);
model.add(
tf.layers.dense({
units: 1
})
);
// Compile for training
model.compile({
optimizer: "adam",
loss: "meanSquaredError"
});
// Train and print timing
console.time("Training");
await model.fit(xs, ys, {
epochs: 30,
callbacks: printCallback,
batchSize: 64
});
console.timeEnd("Training");
// evaluate the model
const next = tf.tensor([7]);
const answer = model.predict(next);
answer.print();
// Cleanup!
answer.dispose();
xs.dispose();
ys.dispose();
model.dispose();
打印的结果张量与49
非常接近。训练成功。虽然这是一次有点奇怪的冒险,但它突出了模型创建和验证过程的一部分。构建模型是你随着时间实验各种数据及其相关解决方案而获得的技能之一。
在接下来的章节中,你将解决更复杂但有益的问题,比如分类。你在这里学到的一切将成为你工作台上的一种工具。
章节回顾
你已经进入了训练模型的世界。层模型结构不仅是一个可理解的视觉,现在它是你可以理解和按需构建的东西。机器学习与普通软件开发非常不同,但你正在逐渐理解 TensorFlow.js 所提供的差异和好处。
章节挑战:模型架构师
现在轮到你通过规范构建一个 Layers 模型了。这个模型做什么?没人知道!它不会用任何数据进行训练。在这个挑战中,你将被要求构建一个具有各种你可能不理解的属性的模型,但你应该足够熟悉以至少设置好模型。这个模型将是你迄今为止创建的最大模型。你的模型将有五个输入和四个输出,它们之间有两层。它看起来像图 8-10。
图 8-10. 章节挑战模型
在你的模型中做以下操作:
-
输入层应该有 5 个单元。
-
下一层应该有 10 个单元并使用sigmoid激活。
-
下一层应该有 7 个单元并使用 ReLU 激活。
-
最后一层应该有 4 个单元并使用softmax激活。
-
模型应该使用 Adam 优化。
-
模型应该使用损失函数
categoricalCrossentropy
。
在构建这个模型并查看摘要之前,你能计算出最终模型将有多少可训练参数吗?这是从图 8-10 中的总行数和圆圈数,不包括输入。
你可以在附录 B 中找到这个挑战的答案。
回顾问题
让我们回顾一下你在本章编写的代码中学到的经验。花点时间回答以下问题:
-
为什么章节挑战模型不适用于本章的训练数据?
-
你可以调用哪个方法来记录和审查模型的结构?
-
为什么要向层添加激活函数?
-
你如何为 Layers 模型指定输入形状?
-
sgd
代表什么? -
什么是一个 epoch?
-
如果一个模型有一个输入,然后是两个节点的一层,和两个节点的输出,那么有多少隐藏层?
这些练习的解决方案可以在附录 A 中找到。
¹ 支持的优化器列在 tfjs-core 的optimizers 文件夹中。
² 从 Andrew Ng 了解更多关于激活函数的知识。
标签:训练,模型,js,lrn,merge,TensorFlow,model,tfjs,数据 From: https://www.cnblogs.com/apachecn/p/18011863