十一、在实时应用中使用机器学习
在这本书里,你已经学习了许多 ML 算法和技术。然而,剩下的就是将这些算法部署到现实世界的应用中。这一章专门介绍在现实世界、实际应用和生产环境中使用 ML 的相关建议。
ML 算法的理想化用法和现实世界的用法有很多不同。在我们的例子中,我们在一个步骤中训练和执行模型,响应一个命令。我们假设模型不需要以任何方式序列化、保存或重新加载。我们没有考虑过用户界面响应、在移动设备上执行或在客户端和服务器之间构建应用编程接口。
实际应用的范围可能比我们讨论的例子大几个数量级。如何训练数据集中有数十亿个数据点的人工神经网络?你如何收集、存储和处理这些信息?
在本章中,我们将讨论以下主题:
- 前端架构
- 后端架构
- 数据流水线
- 您可以用来构建生产 ML 系统的工具和服务
序列化模型
我们在这本书中的例子已经建立、训练和测试了模型,但仅仅在一毫秒后就摧毁了它们。我们可以逃脱惩罚,因为我们的例子使用有限的训练数据,最坏的情况下,只需要几分钟的训练时间。生产应用通常会使用更多的数据,并且需要更多的时间进行培训。在生产应用中,经过训练的模型本身是一种宝贵的资产,应该根据需要进行存储、保存和加载。换句话说,我们的模型必须是可序列化的。
序列化本身通常不是一个难题。模型本质上是训练数据的压缩版本。有些模型确实可能非常大,但它们仍然只是训练它们的数据的一小部分。序列化的主题之所以具有挑战性,是因为它提出了许多您必须考虑的其他体系结构问题,首先是模型存储在哪里以及如何存储的问题。
令人失望的是,没有正确的答案。根据模型的大小、复杂性、使用频率、可用技术等,模型几乎可以存放在任何地方。朴素贝叶斯分类器只需要存储令牌和文档计数,并且只使用键/值查找,没有高级查询,因此单个 Redis 服务器可以托管一个在数十亿个文档上训练的巨大分类器。非常大的模型可以序列化到一个专用的数据库中,甚至可能是一个专用的图形数据库集群。中等大小的模型可以序列化为 JSON 或二进制格式,并存储在数据库 BLOB 字段中,托管在文件服务器或亚马逊 S3 等应用编程接口上,或者如果足够小,存储在浏览器本地存储中。
大多数 ML 库都内置了序列化和反序列化,因为最终这个功能取决于库的实现细节。大多数库包括诸如save()
和load()
之类的方法,尽管您可能想要参考您正在使用的特定库的文档。
在编写自己的库时,请确保包含序列化功能。如果您想要支持多个存储后端,最好将序列化功能从核心逻辑中分离出来,并实现驱动程序和接口架构。
这只是我们需要回答的第一个问题,因为我们已经有了一个可序列化的模型。可序列化模型也是可移植的,这意味着它们可以在机器之间移动。例如,您可以将预训练模型下载到智能手机上,供离线使用。您的 JavaScript 应用可以使用网络工作者来下载和维护语音检测的现成模型,请求麦克风许可,并使网站仅可通过语音命令导航——所有这些都通过 Chrome 扩展实现。
在本节中,我们将讨论一旦您的模型是可序列化和可移植的,就会出现的各种体系结构问题。
服务器上的培训模型
由于训练复杂模型所涉及的时间、数据、处理能力和内存要求,通常希望在服务器上而不是客户机上训练模型。根据用例,甚至模型的评估也可能需要在服务器上进行。
就在哪里训练和在哪里评估模型而言,有几个范例需要考虑。一般来说,您的选择是在服务器上全面培训和评估,在客户端上全面培训和评估,或者在服务器上培训但在客户端上评估。让我们探索每种范式的一些例子。
最简单的实现是在服务器上训练和评估模型。这种方法的主要优点是您可以确定和控制模型的整个执行环境。您可以轻松分析训练和执行模型所需的服务器负载,并根据需要扩展服务器。您完全控制的服务器更容易访问大量的训练数据,因为数据很可能在您也控制的数据库中。您不必担心您的客户端运行的是哪个版本的 JavaScript,或者您是否可以访问客户端的 GPU 进行培训。在服务器上训练和执行模型也意味着由于模型,客户端机器上没有额外的负载。
完全服务器端方法的主要缺点是它需要一个设计良好且健壮的应用编程接口。如果您有一个需要快速响应模型评估的应用,您将需要确保您的 API 能够快速可靠地提供结果。这种方法也意味着不可能对模型进行离线评估;客户端需要连接到服务器才能工作。大多数被宣传为软件即服务 ( SaaS )的应用或产品将使用服务器端模式,如果您向客户提供付费服务,这种方法应该是您首先考虑的方法。
相反,模型可以在客户机上完全训练和评估。在这种情况下,客户端本身需要访问训练数据,并且需要足够的处理能力来训练模型。这种方法通常不适用于需要大量训练集或长训练时间的模型,因为无法确保客户端设备能够处理数据。您还将不得不与可能没有图形处理器或处理能力的旧设备竞争,以训练甚至简单的模型。
但是,对于要求高度数据隐私或数据所有权的应用,如果培训数据来源于设备本身,客户端培训和评估是一种很好的方法。将处理限制在客户端设备上可以确保用户的数据不会传输到任何第三方服务器,并且可以被用户直接删除。指纹扫描、生物特征分析、位置数据分析、电话分析等应用是完全客户端方法的良好候选。这种方法还确保模型可以离线训练和评估,而不需要互联网连接。
在某些情况下,混合方法可以融合两者的优点。需要大量训练数据的高级模型可以在服务器上训练并序列化。当客户端第一次连接到您的应用时,它可以下载并存储训练好的模型供离线使用。客户端本身负责评估模型,但在这种情况下不需要训练模型。
混合方法允许您在服务器上训练和定期更新复杂的模型。序列化模型比原始训练数据小得多,因此可以交付给客户端进行离线评估。只要客户端和服务器都使用兼容的库或算法(也就是说,双方都是TensorFlow.js
),客户端就可以利用服务器的处理能力进行训练,但使用自己的离线处理能力进行要求低得多的评估步骤。
混合模型的一个示例用例是语音或图像识别,可能用于人工智能助手或增强现实 ( 增强现实)应用。在增强现实应用的情况下,服务器负责维护数百万个训练图像,并训练(例如)一个 RNN 来分类对象。培训完成后,客户端可以序列化、存储和下载该模型。
让我们想象一个 AR 应用,它连接到设备的摄像机,并显示一个识别对象的带注释的视频提要。当应用第一次启动时,客户端下载 AR RNN 模型,并将其与版本信息一起存储在设备的本地存储中。当视频提要第一次启动时,应用从存储中检索模型,并将其反序列化到客户端自己的 RNN 实现中。理想情况下,客户端的 RNN 实现将使用与服务器上的库相同的库和版本。
为了对视频的每一帧进行分类和注释,客户端需要在 16 毫秒内完成所有必要的工作(对于 60 FPS 的视频)。这是可以实现的,但实际上并不是每一帧都用于分类;每 3 帧中有 1 帧(相隔 50 毫秒)就足够了。混合方法在这里大放异彩;如果视频的每一帧都必须上传到服务器,进行评估,然后再返回,那么应用将会遭受重大的性能损失。即使有一个出色的模型性能——比如说一个评估时间为 5 毫秒的模型——由于一个 HTTP 请求需要往返时间,您也可能会经历额外的 100 毫秒延迟。
在混合方法下,客户端不需要将图像发送到服务器进行评估,而是可以基于现在加载到内存中的先前训练的模型立即评估图像。一个设计良好的客户端将定期检查服务器的模型更新,并在必要时进行更新,但仍然允许过时的模型离线运行。当应用只是工作的时候,用户是最开心的,混合模式给了你性能和弹性。服务器只依赖于可以异步发生的任务,例如下载更新的模型或将信息发送回服务器。
因此,混合方法最适合需要大型复杂模型的用例,但是模型的评估要么需要非常快速地进行,要么需要离线进行。当然,这不是一个硬性规定。还有许多混合方法最有意义的其他情况;如果您有许多客户端,并且负担不起服务器资源来处理它们的所有评估,您可以使用混合方法来减轻您的处理责任。
设计执行模型训练或评估的客户端应用时必须小心。虽然评估比培训快得多,但它仍然不重要,如果没有正确实施,可能会导致客户端的用户界面性能问题。在下一节中,我们将看到一个名为 web workers 的现代 web 浏览器特性,它可以用来在一个独立的线程中执行处理,保持用户界面的响应性。
网络工作者
如果您正在开发一个网络浏览器应用,您肯定会希望使用一个网络工作人员在后台管理模型。Web workers 是特定于浏览器的功能,旨在允许后台处理,这正是我们在处理大型模型时想要的。
网络工作者可以与XMLHttpRequest
、IndexedDB
、、和postMessage
互动。网络工作者可以用XMLHttpRequest
从服务器下载模型,用IndexedDB
存储在本地,用postMessage
与 UI 线程通信。这三个工具一起使用,为快速响应、高性能和潜在的离线体验提供了完整的基础。其他 JavaScript 平台,如 React Native,在 HTTP 请求、数据存储和进程间通信方面也有类似的能力。
Web workers 可以与其他特定于浏览器的功能(如服务工作人员)和设备 API 相结合,以提供完整的离线体验。服务人员可以缓存特定资产供离线使用,或者在在线和离线评估之间智能切换。浏览器扩展平台以及诸如 React Native 之类的移动平台也提供了许多支持缓存数据、后台线程和离线使用的机制。
不管平台如何,概念都是一样的:当有互联网连接时,应用应该异步下载和上传数据;应用应该缓存(和版本)它运行所需的一切,就像一个预训练的模型;并且应用应该独立于 UI 评估模型。
很容易错误地认为模型足够小,足够快,可以在与用户界面相同的线程中运行。如果您的平均评估时间只有 5 毫秒,并且每 50 毫秒只需要一次评估,那么很容易变得自满,并跳过在单独的线程中评估模型的额外细节。然而,目前市场上的设备种类繁多,因此您甚至无法假设性能有一个数量级的相似性。例如,如果你已经在一部带有图形处理器的现代手机上测试了你的应用,你可能无法准确评估它在一部旧手机的中央处理器上的表现。评估时间可能从 5 毫秒跳到 100 毫秒。在设计不佳的应用中,这将导致用户界面滞后或冻结,但在设计良好的应用中,用户界面将保持响应,但更新频率较低。
幸运的是,网络工作者和postMessage
应用编程接口使用起来很简单。IndexedDB
应用编程接口是一个低级应用编程接口,最初使用可能会令人望而生畏,但是有许多用户友好的库将细节抽象出来。下载和存储预训练模型的具体方式完全取决于应用的实现细节和您选择的具体 ML 算法。较小的型号可以序列化为 JSON 保存在IndexedDB
;更高级的车型可以直接集成到IndexedDB
中。确保在您的服务器端应用编程接口中包含一个比较版本信息的机制;您应该有办法询问服务器模型的当前版本是什么,并且能够将其与您自己的副本进行比较,以便您可以使模型无效并进行更新。
在设计你的网络工作者的消息传递应用编程接口时也要多加考虑。您将使用postMessage
应用编程接口(在所有主要浏览器中都可用)在用户界面线程和后台线程之间进行通信。这种交流至少应该包括一些检查模型状态的方法,以及向模型发送数据点进行评估的方法。但是您也希望期待未来的功能,并使您的应用编程接口灵活且经得起未来考验。
您可能希望规划的两个功能示例是持续改进的模型、基于用户反馈对自身进行再培训的模型,以及学习单个用户的行为或偏好的按用户模型。
持续改进和按用户模型
在应用的整个生命周期中,最终用户很可能会以某种方式与您的模型进行交互。通常,这种交互可以作为反馈来进一步训练模型。交互还可以用于为用户定制模型,根据他们自己的兴趣和行为对其进行定制。
这两个概念的一个很好的例子是垃圾邮件过滤器。随着用户将邮件标记为垃圾邮件,垃圾邮件过滤器应该不断改进。当垃圾邮件过滤器有大量的数据点需要训练时,它们是最强大的,这些数据可以来自应用的其他用户。每当用户将邮件标记为垃圾邮件时,这些知识都应该应用到模型中,其他用户也应该能够享受到他们自己的垃圾邮件过滤的自动改进。
垃圾邮件过滤器也是应该为每个用户定制的模型的一个很好的例子。我认为的垃圾邮件可能和你认为的垃圾邮件不一样。我积极地将我没有注册的营销电子邮件和时事通讯标记为垃圾邮件,但其他用户可能希望在他们自己的收件箱中看到这些类型的邮件。与此同时,有些消息大家都认为是垃圾邮件,因此最好将我们的应用设计为使用一个中央的、不断更新的模型,该模型可以在本地进行优化,以更好地适应特定用户的行为。
贝叶斯分类器非常适合这种描述,因为贝叶斯定理被设计成由新信息更新。在第 5 章、分类算法中,我们讨论了朴素贝叶斯分类器的一个实现,该分类器优雅地处理稀有词。在该方案中,权重因子使单词概率偏向中性,这样稀有单词就不会对模型产生太大影响。每个用户的垃圾邮件过滤器可以使用同样的技术,但是我们可以将单词偏向中心模型的概率,而不是偏向中立。
在这种用法中,罕见的单词权重因子变成了平衡中心模型和局部模型的权重因子。权重因子越大,中心模型就变得越重要,用户影响局部模型所需的时间也就越长。较小的权重因子将更能响应用户的反馈,但也可能导致性能不稳定。在典型的稀有词实现中,权重因子在 3 到 10 的范围内。然而,在每用户模型中,考虑到中心模型是由数百万个例子训练的,并且不应该被少数几个局部例子轻易覆盖,权重因子应该更大——也许是 50-1000。
将数据发送回服务器时必须小心,以便持续改进模型。您不应该将电子邮件信息传输回服务器,因为这会产生不必要的安全风险— 尤其是如果您的产品不是电子邮件托管提供商,而只是电子邮件客户端。如果你也是邮件托管提供商,那么你可以简单的把邮件 ID 发回服务器标记为垃圾邮件,交给模型进行训练;客户端和服务器将分别维护自己的模型。如果您不是电子邮件托管提供商,那么您应该格外小心保护您用户的数据。如果您必须将令牌流传输回服务器,那么您应该在传输过程中对其进行加密并匿名化。您还可以考虑使用令牌化器,在对内容进行令牌化和词干化之后,对令牌进行盐化和哈希化(例如,使用 sha1 或 hmac)。分类器处理散列数据的效果和处理可读数据的效果一样好,但是会增加一层混淆。最后,确保没有记录 HTTP 请求和原始令牌数据。一旦数据进入模型(以令牌计数的形式),它就足够匿名了,但是要确保间谍无法将特定的令牌流与特定的用户联系起来。
当然,朴素贝叶斯分类器并不是唯一可以为每个用户不断更新或定制的模型。对于大多数最大似然算法,模型的持续更新是可能的。如果用户指出 RNN 图像分类错误,则可以将该用户的数据点添加到模型的训练集中,并且可以定期对模型进行完全重新训练,或者可以用更新的训练示例进行批量更新。
一些算法支持模型的真正实时更新。朴素贝叶斯分类器只需要更新令牌和文档计数,它们甚至可以存储在内存中。knn 和 k-means 算法同样允许随时将数据点添加到模型中。有些人工神经网络,像强化学习中使用的人工神经网络,也依赖于实时反馈。
其他算法最好定期分批更新。这些算法通常依赖于梯度下降或随机方法,并且在训练期间需要对许多示例进行反馈循环;例如人工神经网络和随机森林。人工神经网络模型确实可以用单个数据点重新训练,但是批量训练要有效得多。更新模型时,注意不要过度拟合模型;过多的训练并不总是一件好事。
在某些情况下,最好基于更新的训练集对模型进行完全重新训练。这样做的一个原因是避免过度拟合训练数据的短期趋势。通过完全重新训练模型,您可以确保最近的训练示例与旧的训练示例具有相同的权重;这可能是也可能不是所希望的。如果定期自动重新训练模型,请确保训练算法正在查看正确的信号。为了开发可靠的模型,它应该能够平衡精度、损失和方差。由于大部分 ML 训练本质上是随机的,所以不能保证两次训练跑会以相同的质量水平或相似的时间完成。您的训练算法应该控制这些因素,并能够在必要时丢弃不良模型,例如,如果目标精度或损失没有在训练时期数量的最大限制内实现。
此时出现了一个新问题:如何收集、存储和处理千兆字节或万亿字节的训练数据?您如何以及在哪里存储和向客户分发序列化模型?如何从数百万用户中收集新的培训示例?这个主题叫做数据流水线,我们接下来将讨论它。
数据管道
当开发一个生产 ML 系统时,不太可能有现成的训练数据。生产 ML 系统通常是大型应用系统的一部分,您使用的数据可能来自几个不同的来源。ML 算法的训练集可能是您的大型数据库的子集,结合了内容交付网络 ( CDN )上托管的图像和来自弹性搜索服务器的事件数据。在我们的例子中,我们得到了一个独立的训练集,但是在现实世界中,我们需要以自动化和可重复的方式生成训练集。
引导数据通过生命周期各个阶段的过程称为数据流水线。数据流水线可能包括运行 SQL 或 Elasticsearch 对象查询的数据选择器、允许数据从基于事件或日志的数据流入的事件订阅、聚合、连接、将数据与来自第三方 API 的数据相结合、清理、规范化和存储。
在一个理想的实现中,数据管道充当了更大的应用环境和 ML 进程之间的抽象层。ML 算法应该能够读取数据管道的输出,而不需要知道数据的原始来源,类似于我们的例子。在这种方法下,ML 算法将不需要了解应用的实现细节;管道本身负责了解应用是如何构建的。
因为有许多可能的数据源和无限的方法来构建应用,所以没有一个放之四海而皆准的数据管道。但是,大多数数据管道将包含这些组件,我们将在以下章节中讨论:
- 数据查询和事件订阅
- 数据连接或聚合
- 转换和规范化
- 存储和交付
让我们来看看这些概念,并介绍一些可以实现它们的工具和技术。
数据查询
想象一下像 Disqus 这样的应用,这是一个可嵌入的评论表单,网站所有者可以使用它来为博客文章或其他页面添加评论功能。Disqus 的主要功能是允许用户喜欢或在帖子上留下评论,然而,作为一个额外的功能和收入来源,Disqus 可以进行内容推荐,并将其与赞助内容一起显示。内容推荐系统是 ML 系统的一个例子,它只是一个更大的应用的一个特征。
诸如 Disqus 之类的应用中的内容推荐系统不一定需要与评论数据交互,而是可以使用用户的喜好历史来生成类似于当前页面的推荐。这样的系统还需要分析喜欢的页面的文本内容,并将其与网络中所有页面的文本内容进行比较,以便做出推荐。Disqus 不需要帖子的内容来提供评论功能,但是需要在数据库中存储关于页面的元数据(比如它的网址和标题)。因此,帖子内容可能不在应用的主数据库中,尽管喜欢和页面元数据可能存储在那里。
围绕 Disqus 推荐系统构建的数据管道首先需要在主数据库中查询用户喜欢的页面——或者喜欢当前页面的用户喜欢的页面——并返回它们的元数据。然而,为了找到相似的内容,系统将需要使用每个喜欢的帖子的文本内容。这些数据可能存储在一个单独的系统中,可能是一个辅助数据库,如 MongoDB 或 Elasticsearch,或者是亚马逊 S3 或其他数据仓库。管道需要根据主数据库返回的元数据检索文本内容,并将内容与元数据相关联。
这是数据管道早期阶段的多个数据选择器或数据源的示例。一个数据源是主应用数据,它存储帖子和喜欢的元数据。另一个数据源是存储文章文本内容的辅助服务器。
这条管道的下一步可能包括找到一些与用户喜欢的帖子相似的候选帖子,可能是通过请求弹性搜索或其他可以找到相似内容的服务。然而,相似的内容不一定是正确的服务内容,因此这些候选文章最终将由(假设的)人工神经网络进行排名,以确定显示的最佳内容。在这个例子中,数据管道的输入是当前页面,数据管道的输出是一个列表,比如说 200 个相似的页面,然后人工神经网络将对这些页面进行排序。
如果所有必要的数据都驻留在主数据库中,那么整个管道可以通过一条 SQL 语句和一些连接来实现。即使在这种情况下,也应该注意在 ML 算法和数据管道之间开发一定程度的抽象,因为您可能会决定在将来更新应用的体系结构。然而,在其他情况下,数据将驻留在不同的位置,应该开发一个更考虑的管道。
有许多方法可以构建这个数据管道。您可以开发一个执行所有管道任务的 JavaScript 模块,在某些情况下,您甚至可以使用标准的 Unix 工具编写一个 bash 脚本来完成任务。在复杂性范围的另一端,有专门构建的数据流水线工具,如 Apache Kafka 和 AWS 流水线。这些系统是模块化设计的,允许您定义特定的数据源、查询、转换和聚合模块以及连接它们的工作流。例如,在 AWS 管道中,您定义了数据节点,这些节点了解如何与应用中的各种数据源进行交互。
管道的最早阶段通常是某种数据查询操作。训练示例必须从更大的数据库中提取,请记住,不是数据库中的每条记录都一定是训练示例。例如,在垃圾邮件过滤器的情况下,您应该只选择被用户标记为垃圾邮件或非垃圾邮件的邮件。垃圾邮件过滤器自动标记为垃圾邮件的邮件可能不应该用于培训,因为这可能会导致正反馈循环,最终导致不可接受的假阳性率。
同样,您可能希望防止被您的系统阻止或禁止的用户影响您的模型训练。一个糟糕的演员可能会通过对他们自己的数据采取不适当的行动来故意误导 ML 模型,所以你应该取消这些数据点作为训练示例的资格。
或者,如果您的应用要求最近的数据点优先于较旧的训练点,则您的数据查询操作可能会对要用于训练的数据设置基于时间的限制,或者选择按时间倒序排列的固定限制。无论情况如何,请确保您仔细考虑您的数据查询,因为它们是数据管道中必不可少的第一步。
然而,并不是所有的数据都需要来自数据库查询。许多应用使用发布/订阅或事件订阅架构来捕获流数据。这些数据可以是从多个服务器聚合的活动日志,也可以是来自多个来源的实时事务数据。在这些情况下,事件订阅者将是数据管道的早期部分。请注意,事件订阅和数据查询不是互斥的操作。通过发布/订阅系统进入的事件仍然可以基于各种标准进行过滤;这仍然是一种数据查询形式。
事件订阅模型的一个潜在问题出现在它与批处理训练方案相结合的时候。如果您需要 5,000 个数据点,但每秒仅接收 100 个数据点,则您的管道需要保持一个数据点缓冲区,直到达到目标大小。有各种各样的消息队列系统可以帮助实现这一点,比如 RabbitMQ 或 Redis。需要这种功能的管道可能会将消息保存在队列中,直到达到 5,000 条消息的目标,然后才释放消息,通过管道的其余部分进行批处理。
在从多个来源收集数据的情况下,很可能需要以某种方式进行连接或聚合。现在让我们看一下数据需要从外部应用编程接口连接到数据的情况。
数据连接和聚合
让我们回到 Disqus 内容推荐系统的例子。假设数据管道能够直接从主数据库中查询赞和帖子元数据,但是应用中没有系统存储帖子的文本内容。相反,微服务是以应用编程接口的形式开发的,它接受帖子标识或网址,并返回页面的净化文本内容。
在这种情况下,数据管道将需要与微服务应用编程接口交互,以便获得每个帖子的文本内容。这种方法是完全有效的,尽管如果发布内容请求的频率很高,可能应该实现一些缓存或存储。
数据管道需要采用类似于事件订阅模型中消息缓冲的方法。管道可以使用消息队列对仍然需要内容的帖子进行排队,并为队列中的每个帖子向内容微服务发出请求,直到队列耗尽。当检索到每个帖子的内容时,它会被添加到帖子元数据中,并存储在一个单独的队列中,等待完成的请求。只有当源队列耗尽并且接收队列已满时,管道才应继续下一步。
数据连接不一定需要涉及微服务应用编程接口。如果管道从需要组合的两个独立来源收集数据,可以采用类似的方法。管道是唯一需要理解两个数据源和格式之间关系的组件,让数据源和 ML 算法独立于这些细节运行。
当需要数据聚合时,队列方法也能很好地工作。这种情况的一个例子是管道,其中输入是流输入数据,输出是令牌计数或值聚合。在这些情况下,使用消息队列是可取的,因为大多数消息队列确保消息只能被使用一次,从而防止聚合器进行任何复制。当事件流非常频繁时,这一点尤其有价值,因为在每个事件到来时对其进行标记会导致备份或服务器过载。
因为消息队列确保每条消息只被使用一次,所以高频事件数据可以直接流入一个队列,在该队列中消息被多个工作人员并行使用。每个工作人员可能负责标记事件数据,然后将标记流推送到不同的消息队列。消息队列软件确保没有两个工作人员处理同一个事件,每个工作人员都可以作为一个独立的单元运行,只关心令牌化。
当令牌化器将结果推送到新的消息队列中时,另一个工作人员可以使用这些消息并聚合令牌计数,每一秒、每一分钟或每 1000 个事件将自己的结果传递到管道的下一步,无论哪一个适合应用。例如,这种类型的管道的输出可能会被输入到一个不断更新的贝叶斯模型中。
以这种方式设计的数据管道的一个好处是性能。如果您试图在一个系统中订阅高频事件数据、标记每条消息、聚合令牌计数和更新模型,您可能会被迫使用一台非常强大(且昂贵)的服务器。服务器将同时需要高性能的中央处理器、大量的内存和高吞吐量的网络连接。
但是,通过将管道分成多个阶段,您可以针对特定的任务和负载条件优化管道的每个阶段。接收源事件流的消息队列只需要接收事件流,而不需要处理它。令牌化器工作人员不一定需要是高性能服务器,因为他们可以并行运行。聚合队列和工作者将处理大量数据,但不需要将数据保留几秒钟以上,因此可能不需要太多内存。最终模型是源数据的压缩版本,可以存储在一台更普通的机器上。数据管道的许多组件可以由商用硬件构建,这仅仅是因为数据管道鼓励模块化设计。
在许多情况下,您需要在整个管道中将数据从一种格式转换为另一种格式。这可能意味着从本地数据结构转换到 JSON,转换或插入值,或者散列值。现在让我们讨论数据管道中可能发生的几种类型的数据转换。
转换和规范化
当您的数据通过管道时,可能需要将其转换为与您的算法输入层兼容的结构。可以对管道中的数据执行许多可能的转换。例如,为了在敏感用户数据到达基于令牌的分类器之前对其进行保护,您可以对令牌应用加密哈希函数,以便它们不再是人类可读的。
更典型的是,转换的类型与净化、规范化或转换相关。清理操作可能包括删除不必要的空白或 HTML 标记,从令牌流中删除电子邮件地址,以及从数据结构中删除不必要的字段。如果您的管道订阅了一个事件流作为数据源,并且事件流将源服务器的 IP 地址附加到事件数据,那么从数据结构中删除这些值将是一个好主意,这样既可以节省空间,又可以最大限度地减少潜在的数据泄漏。
同样,如果分类算法不需要电子邮件地址,管道应该删除该数据,以便它与尽可能少的服务器和系统交互。如果您已经设计了一个垃圾邮件过滤器,您可能希望只使用电子邮件地址的域部分,而不是完全限定的地址。或者,电子邮件地址或域可以由管道散列,使得分类器仍然可以识别它们,但是人类不能。
确保审核您的数据是否存在其他潜在的安全和隐私问题。如果您的应用收集最终用户的 IP 地址作为其事件流的一部分,但分类器不需要该数据,请尽早将其从管道中删除。随着新的欧洲隐私法的实施,这些考虑变得越来越重要,每个开发人员都应该意识到隐私和合规性问题。
数据转换的一个常见类别是规范化。当处理给定字段或特征的数值范围时,通常希望对该范围进行规范化,使其具有已知的最小和最大界限。一种方法是将同一字段的所有值归一化到范围[0,1],使用遇到的最大值作为除数(例如,序列 1,2,4 可以归一化到 0.25,0.5,1 )。数据是否需要以这种方式规范化将完全取决于消耗数据的算法。
另一种标准化方法是将值转换为百分位数。在这个方案中,非常大的外围值不会使算法产生太大的偏差。如果大多数值介于 0 到 100 之间,但有几个点包含 50,000 之类的值,则算法可能会给予大值过大的优先级。但是,如果将数据规范化为百分位数,则保证不会有任何超过 100 的值,并且异常值将与其余数据进入相同的范围。这是不是好事,就看算法了。
数据管道也是计算派生要素或二阶要素的好地方。想象一个随机的森林分类器,它使用 Instagram 配置文件数据来确定该配置文件属于人类还是机器人。Instagram 个人资料数据将包括用户的关注者数量、朋友数量、帖子数量、网站、个人简历和用户名等字段。一个随机的森林分类器将很难在其原始表示中使用这些字段,但是,通过应用一些简单的数据转换,您可以达到 90%的准确率。
在 Instagram 案例中,一种有用的数据转换是计算比率。追随者计数和朋友计数,作为单独的特征或信号,可能对分类器没有用处,因为它们在某种程度上被独立对待。但是好友与关注者的比率可能会成为暴露机器人用户的一个非常强烈的信号。一个有 1000 个朋友的 Instagram 用户不会升起任何旗帜,一个有 50 个粉丝的 Instagram 用户也不会;独立对待,这些特征并不是强信号。然而,一个 Instagram 用户的好友与关注者比例为 20(或 1000/50),几乎可以肯定是一个专为关注其他用户而设计的机器人。类似地,像帖子对关注者或帖子对朋友这样的比率,可能最终会成为比这些特性中任何一个都更强的信号。
Instagram 用户的个人资料简历、网站或用户名等文本内容也可以通过从中获得二阶特征而变得有用。一个分类器可能无法对一个网站的网址做任何事情,但是也许一个布尔的 has _ profile _ site特性可以作为一个信号。如果,在你的研究中,你注意到机器人的用户名中有很多数字,你可以从用户名本身获得特征。一个特性可以计算用户名中字母与数字的比例,另一个布尔特性可以表示用户名的末尾或开头是否有数字,一个更高级的特性可以确定用户名中是否使用了字典单词(因此区分@themachinelearningwriter
和类似@panatoe234
的乱码)。
派生特征可以是任何复杂或简单的级别。另一个简单的功能可能是 Instagram 配置文件是否在配置文件生物字段中包含网址(与专用网站字段相反);这可以通过正则表达式和用作特征的布尔值来检测。更高级的功能可以自动检测用户内容中使用的语言是否与用户区域设置指定的语言相同。如果用户声称他们在法国,但总是用俄语写标题,那可能确实是一个住在法国的俄罗斯人,但当与其他信号结合时,如朋友与追随者的比率远低于 1,这些信息可能表明是机器人用户。
可能还需要对管道中的数据应用较低级别的转换。如果源数据是 XML 格式,但是分类器需要 JSON 格式,那么管道应该负责格式的解析和转换。
也可以应用其他数学变换。如果数据的原生格式是面向行的,但分类器需要面向列的数据,则管道可以执行向量换位操作作为处理的一部分。
同样,管道可以使用数学插值来填充缺失的值。如果您的管道订阅了由实验室环境中的一组传感器发出的事件,并且单个传感器离线进行了几次测量,则在两个已知值之间进行插值以填充缺失的数据可能是合理的。在其他情况下,缺失值可以用总体的平均值或中值来代替。用平均值或中值替换缺失值通常会导致分类器对该数据点的特征进行降级,而不是通过给它一个空值来破坏分类器。
总的来说,在数据管道内的转换和规范化方面需要考虑两件事。首先是源数据和目标格式的机械细节:XML 数据必须转换为 JSON,行必须转换为列,图像必须从 JPEG 转换为 BMP 格式,等等。机械细节并不太棘手,因为你已经知道系统所需的源和目标格式。
另一个考虑是数据的语义或数学转换。这是特征选择和特征工程的练习,不像机械变换那样简单。确定要导出哪些二阶特征既是艺术也是科学。艺术是为衍生特征提出新的想法,科学是对你的作品进行严格的测试和实验。例如,在我的 Instagram 机器人检测经验中,我发现 Instagram 用户名中的字母数字比是一个非常弱的信号。为了避免给问题增加不必要的维度,我在做了一些实验后放弃了这个想法。
在这一点上,我们有一个假设的数据管道,它收集数据,连接和聚合数据,处理数据,并将其规范化。我们差不多完成了,但是数据仍然需要传递给算法本身。一旦算法被训练好,我们可能还想序列化模型并存储它以备后用。在下一节中,我们将讨论在传输和存储训练数据或序列化模型时需要考虑的几个问题。
存储和传递数据
一旦您的数据管道应用了所有必要的处理和转换,它还有一个任务要做:将数据传递给您的算法。理想情况下,算法不需要知道数据管道的实现细节。该算法应该有一个单一的位置,它可以互动,以获得充分处理的数据。该位置可以是磁盘上的文件、消息队列、亚马逊 S3 等服务、数据库或应用编程接口端点。您选择的方法将取决于您可用的资源、服务器系统的拓扑或体系结构以及数据的格式和大小。
定期训练的模型通常是最容易处理的情况。如果您正在开发一个图像识别 RNN,它可以学习许多图像的标签,并且只需要每隔几个月重新培训一次,那么一个好的方法是将所有图像以及清单文件(将图像名称与标签相关联)存储在亚马逊 S3 等服务中,或者存储在磁盘上的专用路径中。该算法将首先加载和解析清单文件,然后根据需要从存储服务加载图像。
同样,Instagram bot 检测算法可能只需要每周或每月重新训练一次。该算法可以直接从数据库表或存储在 S3 或本地磁盘上的 JSON 或 CSV 文件中读取训练数据。
很少需要这样做,但是在一些奇特的数据管道实现中,您也可以为算法提供一个作为微服务构建的专用 API 端点;该算法将简单地首先向 API 端点查询训练点引用列表,然后依次向 API 请求每个引用。
另一方面,需要在线更新或接近实时更新的模型最好由消息队列提供服务。如果贝叶斯分类器需要实时更新,算法可以订阅消息队列,并在更新到来时应用更新。即使在使用复杂的多级管道时,如果您已经很好地设计了所有组件,也有可能在几分之一秒内处理新数据并更新模型。
回到垃圾邮件过滤器的例子,我们可以这样设计一个高性能的数据管道:首先,一个 API 端点接收来自用户的反馈。为了保持用户界面的响应性,这个 API 端点只负责将用户的反馈放入消息队列中,并且可以在不到一毫秒的时间内完成任务。数据管道依次订阅消息队列,并在几毫秒内意识到新消息。然后,管道会对消息应用一些简单的转换,如标记化、词干化,甚至可能对标记进行哈希处理。
管道的下一阶段将令牌流转换为令牌及其计数的哈希表(例如,从嘿嘿那里到{嘿:2,那里:1 });这避免了分类器需要多次更新同一个令牌的计数。这个处理阶段最多只需要几毫秒。最后,完全处理的数据被放在分类器订阅的单独的消息队列中。一旦分类器知道了数据,它就可以立即将更新应用到模型中。例如,如果分类器由 Redis 支持,这个最后阶段也只需要几毫秒。
我们所描述的整个过程,从用户的反馈到达 API 服务器到模型更新的时间,可能只需要 20 ms,考虑到通过互联网(或任何其他方式)的通信受到光速的限制,TCP 数据包在纽约和旧金山之间往返的最佳情况是 40 ms 实际上,良好的互联网连接的平均越野延迟约为 80 毫秒。因此,我们的数据管道和模型能够根据用户反馈更新自己整整 20 毫秒,然后用户甚至会收到他们的 HTTP 响应。
并非每个应用都需要实时处理。为一个应用编程接口、一个数据管道、消息队列、一个 Redis 存储管理单独的服务器,以及托管分类器,在工作量和预算方面可能都是过度的。您必须确定什么最适合您的用例。
最后要考虑的事情与数据管道无关,而是模型本身的存储和交付,在混合方法中,模型在服务器上训练,但在客户机上评估。首先要问自己的问题是,这个模式被认为是公共的还是私人的。例如,私人模型不应该存储在公共的亚马逊 S3 桶中;相反,S3 桶应该有适当的访问控制规则,您的应用将需要获得一个有到期时间的签名下载链接(S3 API 对此有所帮助)。
接下来要考虑的是模型有多大,客户端下载的频率有多高。如果公共模型经常下载,但很少更新,最好使用 CDN,以便利用边缘缓存。例如,如果您的模型存储在亚马逊 S3 上,那么亚马逊云前端 CDN 将是一个不错的选择。
当然,您可以随时构建自己的存储和交付解决方案。在这一章中,我假设了一个云架构,但是如果您有一个专用的或并置的服务器,那么您可能只想将序列化的模型存储在磁盘上,并通过您的网络服务器软件或应用的应用编程接口提供服务。在处理大型模型时,确保考虑如果许多用户试图同时下载模型会发生什么。如果同时有太多人请求文件,您可能会无意中使服务器的网络连接饱和,您可能会超出服务器 ISP 设置的任何带宽限制,或者您可能会在服务器移动数据时导致服务器的 CPU 陷入输入/输出等待。
如前所述,数据流水线没有一个放之四海而皆准的解决方案。如果你是一个为了好玩而开发应用的爱好者,或者只是一些用户,你有很多数据存储和传输的选择。但是,如果您以专业身份从事大型企业项目,您将不得不考虑数据管道的各个方面,以及它们将如何影响应用的性能。
我将向阅读这一部分的爱好者提供最后一条建议。虽然爱好项目确实不需要复杂的实时数据管道,但无论如何都应该构建一个。能够设计和构建实时数据管道是一项很有市场价值的技能,但拥有这项技能的人并不多,如果你愿意在实践中学习 ML 算法,那么你也应该练习构建高性能的数据管道。我并不是说你应该为每一个爱好项目构建一个大的、花哨的数据管道——只是说你应该使用几种不同的方法做几次,直到你不仅对概念而且对实现都感到满意。熟能生巧,熟能生巧就是把手弄脏。
摘要
在本章中,我们讨论了许多与生产中的 ML 应用相关的实际问题。学习 ML 算法当然是构建 ML 应用的核心,但是构建一个应用不仅仅是实现一个算法。应用最终需要通过各种设备与用户交互,因此仅考虑您的应用做什么是不够的— 您还必须计划如何以及在哪里使用它。
我们以关于可序列化和可移植模型的讨论开始了这一章,您了解了模型训练和评估的不同架构方法。我们讨论了完全服务器端方法(SaaS 产品常见)、完全客户端方法(对敏感数据有用)和混合方法,通过这种方法,模型在服务器上进行训练,但在客户端进行评估。您还了解了 web workers,这是一个有用的特定于浏览器的特性,在客户端评估模型时,您可以使用它来确保用户界面的性能和响应。
我们还讨论了持续更新或定期重新训练的模型,以及在客户机和服务器之间传递反馈的各种方法。您还了解了每个用户的模型,或算法,它们可以由一个真实的中心来源训练,但由单个用户的特定行为提炼。
最后,您学习了数据管道以及管理从一个系统到下一个系统的数据收集、组合、转换和交付的各种机制。我们的数据管道讨论的一个中心主题是使用数据管道作为 ML 算法和您的生产系统的其余部分之间的抽象层的概念。
我想讨论的最后一个话题是很多 ML 学生都很好奇的一个问题:对于给定的问题,你到底是如何选择正确的 ML 算法的?ML 领域的专家通常会形成一种直觉来指导他们的决策,但这种直觉可能需要数年才能形成。在下一章中,我们将讨论实用技术,您可以使用这些技术来缩小适用于任何给定问题的 ML 算法的范围。
版权属于:月萌API www.moonapi.com,转载请注明出处