🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎
📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝
📣系列专栏 - 机器学习【ML】 自然语言处理【NLP】 深度学习【DL】
🖍foreword
✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。
如果你对这个系列感兴趣的话,可以关注订阅哟👋
文章目录
什么时候在设计数据库或分布式系统时,软件工程师关注的是容错性,即系统在其某些组件发生故障时继续工作的能力。在软件中,问题不在于系统的给定部分是否会失败,而是何时会失败。相同的原则可以应用于 ML。无论模型有多好,它都会在某些示例上失败,因此您应该设计一个能够优雅地处理此类失败的系统。
在本章中,我们将介绍帮助预防或减轻故障的不同方法。首先,我们将了解如何验证我们接收和生成的数据的质量,并使用此验证来决定如何向用户显示结果。然后,我们将研究如何使建模管道更加健壮,以便能够有效地为许多用户提供服务。之后,我们将查看利用用户反馈的选项并判断模型的性能。我们将以对 Chris Moody 的关于部署最佳实践的采访结束本章。
工程师围绕失败
让我们涵盖一些最有可能导致 ML 管道失败的方法。细心的读者会注意到,这些失败案例与我们在“调试接线:可视化和测试”中看到的调试技巧有些相似。事实上,在生产环境中向用户公开模型会带来一系列挑战,这些挑战与调试模型所面临的挑战相似。
错误和错误可能出现在任何地方,但验证三个领域尤为重要:管道的输入、模型的置信度以及它产生的输出。让我们按顺序解决每个问题。
输入和输出检查
任何给定的模型是在表现出特定特征的特定数据集上训练的。训练数据具有一定数量的特征,并且这些特征中的每一个都属于特定类型。此外,每个特征都遵循模型为了准确执行而学习的给定分布。
正如我们在“新鲜度和分布转变”中看到的那样,如果生产数据与训练模型的数据不同,模型可能难以执行。为此,您应该检查管道的输入。
检查输入
一些当面对数据分布的微小差异时,模型可能仍然表现良好。但是,如果一个模型接收到的数据与其训练数据有很大不同,或者如果某些特征缺失或属于意外类型,它将难以执行。
正如我们之前看到的,即使输入不正确(只要这些输入的形状和类型正确),ML 模型也能够运行。模型会产生输出,但这些输出可能非常不正确。考虑图 10-1中所示的示例。管道首先将句子矢量化并在矢量化表示上应用分类模型,从而将句子分类为两个主题之一。如果管道接收到一串随机字符,它仍然会将其转换为向量,模型会进行预测。这种预测是荒谬的,但是仅仅通过观察模型的结果是没有办法知道的。
图 10-1。模型仍将输出对随机输入的预测
为了防止模型在不正确的输出上运行,我们需要在将这些输入传递给模型之前检测这些输入是否不正确。
检查与测试
在本节中,我们谈论的是输入检查,而不是我们在“测试您的 ML 代码”中看到的输入测试。区别很微妙但很重要。测试验证代码在给定已知的预定输入的情况下是否按预期运行。每次代码或模型更改时通常都会运行测试以验证管道是否仍然正常工作。本节中的输入检查是管道本身的一部分,并根据输入质量更改程序的控制流。失败的输入检查可能会导致运行不同的模型或根本不运行模型。
这些检查涵盖与“测试您的 ML 代码”中的测试类似的领域。按照重要性顺序,他们将:
-
验证是否存在所有必要的功能
-
检查每个功能类型
-
验证特征值
孤立地验证特征值可能很困难,因为特征分布可能很复杂。执行此类验证的一种简单方法是定义一个特征可以采用的合理值范围,并验证它是否落在该范围内。
如果任何输入检查失败,则模型不应运行。您应该做什么取决于用例。如果丢失的数据代表了一条核心信息,您应该返回一个错误,说明错误的来源。如果你估计你仍然可以提供一个结果,你可以用启发式代替模型调用。这是通过构建启发式来启动任何 ML 项目的另一个原因;它为您提供了一个后退的选择!
在图 10-2中,您可以看到此逻辑的示例,其中采用的路径取决于输入检查的结果。
图 10-2。输入检查的示例分支逻辑
以下是来自 ML Editor 的一些控制流逻辑示例,用于检查缺失的特征和特征类型。根据输入的质量,它要么引发错误,要么运行试探法。我在这里复制了示例,但您也可以在 本书的 GitHub 存储库中找到它以及其余的 ML Editor 代码。
def validate_and_handle_request(question_data):
missing = find_absent_features(question_data)
if len(missing) > 0:
raise ValueError("Missing feature(s) %s" % missing)
wrong_types = check_feature_types(question_data)
if len(wrong_types) > 0:
# If data is wrong but we have the length of the question, run heuristic
if "text_len" in question_data.keys():
if isinstance(question_data["text_len"], float):
return run_heuristic(question_data["text_len"])
raise ValueError("Incorrect type(s) %s" % wrong_types)
return run_model(question_data)
验证模型输入允许您缩小故障模式并识别数据输入问题。接下来,您应该验证模型的输出。
模型输出
一次模型做出预测后,您应该确定是否应将其显示给用户。如果预测超出模型可接受的答案范围,您应该考虑不显示它。
例如,如果您根据照片预测用户的年龄,则输出值应介于 0 到 100 多岁之间(如果您在 3000 年阅读本书,请随意调整界限)。如果模型输出的值超出此范围,则不应显示它。
在这种情况下,可接受的结果不仅由合理的结果来定义。它还取决于您对对我们的用户有用的结果类型的估计。
对于我们的 ML 编辑器,我们只想提供可操作的建议。如果一个模型预测用户写的所有内容都应该被完全删除,这将包含一个相当无用(和侮辱)的建议。以下是验证模型输出并在必要时恢复为启发式的示例片段:
def validate_and_correct_output(question_data, model_output):
# Verify type and range and raise errors accordingly
try:
# Raises value error if model output is incorrect
verify_output_type_and_range(model_output)
except ValueError:
# We run a heuristic, but could run a different model here
run_heuristic(question_data["text_len"])
# If we did not raise an error, we return our model result
return model_output
当模型失败时,您可以恢复到我们之前看到的启发式方法,或者恢复到您之前构建的更简单的模型。尝试早期类型的模型通常是值得的,因为不同的模型可能有不相关的错误。
我已经在图 10-3中的玩具示例中说明了这一点。在左侧,您可以看到具有更复杂决策边界的性能更好的模型。在右侧,您可以看到一个更糟糕、更简单的模型。较差的模型会犯更多的错误,但由于其决策边界的形状不同,其错误与复杂模型不同。正因为如此,更简单的模型得到了一些正确的例子,而复杂的模型却出错了。这就是为什么在主模型失败时使用简单模型作为备份是一个合理想法的直觉。
如果您确实使用更简单的模型作为备份,您还应该以相同的方式验证其输出并回退到启发式或如果它们未通过您的检查则显示错误。
验证模型的输出是否在合理范围内是一个好的开始,但这还不够。在下一节中,我们将介绍我们可以围绕模型构建的额外保护措施。
图 10-3。更简单的模型通常会出现不同的错误
模型失败回退
我们建立了检测和纠正错误输入和输出的安全措施。然而,在某些情况下,我们模型的输入可能是正确的,而我们模型的输出可能是合理的,但完全错误。
回到从照片预测用户年龄的例子,保证模型预测的年龄是合理的人类年龄是一个好的开始,但理想情况下我们希望预测这个特定用户的正确年龄。
没有一个模型会 100% 正确,轻微的错误通常是可以接受的,但您应该尽可能地检测模型何时出错。这样做可以让您潜在地将给定案例标记为太难,并鼓励用户提供更简单的输入(例如,以光线充足的照片的形式)。
那里是检测错误的两种主要方法。最简单的方法是跟踪模型的置信度以估计输出是否准确。第二个是构建一个附加模型,其任务是检测主模型可能失败的示例。
对于第一种方法,分类模型可以输出一个概率,该概率可用作模型对其输出的置信度的估计。如果这些概率得到很好的校准(参见“校准曲线”),它们可用于检测模型不确定的情况并决定不向用户显示结果。
有时,尽管为示例分配了很高的概率,但模型还是错误的。这就是第二种方法的用武之地:使用模型过滤掉最难的输入。
过滤模型
除了不总是值得信赖之外,使用模型的置信度分数还有另一个严重的缺点。为了获得这个分数,无论是否使用其预测,都需要运行整个推理管道。例如,当使用需要在 GPU 上运行的更复杂的模型时,这尤其浪费。理想情况下,我们希望在不运行模型的情况下估计模型在示例上的表现如何。
这就是过滤模型背后的想法。由于您知道模型难以处理某些输入,因此您应该提前检测它们,而不用在它们上运行模型。过滤模型是输入测试的 ML 版本。它是一个二元分类器,经过训练可以预测模型是否会在给定示例上表现良好。这种模型之间的核心假设是,对于主要模型来说很难的数据点类型存在趋势。如果这些困难的例子有足够多的共同点,过滤模型可以学习将它们与更简单的输入分开。
以下是您可能希望过滤模型捕获的一些类型的输入:
-
与主模型在其上表现良好的输入在质量上不同的输入
-
模型接受过训练但遇到困难的输入
-
意在愚弄主要模型的对抗性输入
在图 10-4中,您可以看到图 10-2中逻辑的更新示例,其中现在包含一个过滤模型。如您所见,过滤模型仅在输入检查通过时运行,因为您只需要过滤掉可能进入“运行模型”框的输入。
要训练过滤模型,您只需收集包含两类示例的数据集;您的主要模型成功的类别和它失败的其他类别。这可以使用我们的训练数据来完成,不需要额外的数据收集!
图 10-4。在我们的输入检查中添加过滤步骤(粗体)
在图 10-5中,我展示了如何通过利用经过训练的模型及其在数据集上的结果来做到这一点,如左图所示。对模型正确预测的一些数据点和模型预测失败的一些数据点进行采样。然后,您可以训练一个过滤模型来预测哪些数据点是原始模型失败的数据点。
图 10-5。获取过滤模型的训练数据
一旦你有了训练有素的分类器,训练过滤模型就相对简单了。给定一个测试集和一个训练有素的分类器,下面的函数就可以做到这一点。
def get_filtering_model(classifier, features, labels):
"""
Get prediction error for a binary classification dataset
:param classifier: trained classifier
:param features: input features
:param labels: true labels
"""
predictions = classifier.predict(features)
# Create labels where errors are 1, and correct guesses are 0
is_error = [pred != truth for pred, truth in zip(predictions, labels)]
filtering_model = RandomForestClassifier()
filtering_model.fit(features, is_error)
return filtering_model
这个方法被谷歌用于他们的智能回复功能,它建议对收到的电子邮件进行一些简短的回复(参见 A. Kanan 等人的这篇文章,“智能回复:电子邮件的自动回复建议”)。他们使用他们所谓的触发模型,负责决定是否运行建议响应的主模型。在他们的案例中,只有大约 11% 的电子邮件适合这种模式。通过使用过滤模型,他们将基础设施需求减少了一个数量级。
过滤模型通常需要满足两个标准。它应该很快,因为它的全部目的是减少计算负担,并且应该善于消除困难的情况。
试图识别疑难案例的过滤模型不需要能够捕获所有案例;它只需要检测到足以证明在每次推理时运行它的额外成本是合理的。通常,过滤模型越快,所需的效率就越低。原因如下:
假设您仅使用一个模型的平均推理时间是i.
使用过滤模型的平均推理时间为f+i(1-b)其中f是过滤模型的执行时间,b是它过滤掉的示例的平均比例(b 代表块)。
要通过使用过滤模型减少平均推理时间,您需要具备f+i(1-b)<i, 转化为fi<b.
这意味着您的模型过滤掉的案例比例需要高于其推理速度与较大模型速度之间的比率。
例如,如果您的过滤模型比常规模型快 20 倍(fi=5%), 它需要阻止超过 5% 的案例 (5%<b) 在生产中有用。
当然,您还需要确保过滤模型的精度良好,这意味着它阻止的大部分输入实际上对您的主模型来说太难了。
一种方法是定期让一些示例通过您的过滤模型会阻止并检查您的主要模型如何处理它们。我们将在“选择要监控的内容”中更深入地介绍这一点。
由于过滤模型不同于推理模型,专门为预测困难案例而训练,因此它可以比依赖主模型的概率输出更准确地检测这些案例。因此,使用过滤模型有助于降低出现不良结果的可能性并提高资源利用率。
由于这些原因,将过滤模型添加到现有的输入和输出检查中可以显着提高生产管道的稳健性。在下一节中,我们将通过讨论如何将 ML 应用程序扩展到更多用户以及如何组织复杂的训练过程来解决更多使管道健壮的方法。
性能工程师
保持性能将模型部署到生产中是一项重大挑战,尤其是当产品变得越来越流行并且模型的新版本会定期部署时。我们将通过讨论允许模型处理大量推理请求的方法来开始本节。然后,我们将介绍可以更轻松地定期部署更新模型版本的功能。最后,我们将讨论通过使训练管道更具可重复性来减少模型之间性能差异的方法。
扩展到多个用户
许多软件工作负载是水平可扩展的,这意味着增加服务器是在请求数量增加时保持合理响应时间的有效策略。ML 在这方面没有什么不同,因为我们可以简单地启动新服务器来运行我们的模型并处理额外的容量。
笔记
如果您使用深度学习模型,您可能需要 GPU 才能在可接受的时间内提供结果。如果是这种情况,并且您希望有足够多的请求来需要多个支持 GPU 的机器,则您应该在两个不同的服务器上运行您的应用程序逻辑和模型推理。
由于 GPU 实例通常比大多数云提供商的常规实例贵一个数量级,因此使用一个更便宜的实例扩展您的应用程序,而 GPU 实例仅处理推理将显着降低您的计算成本。使用此策略时,您应该记住,您正在引入一些通信开销,并确保这不会对您的用例造成太大损害。
除了增加资源分配外,机器学习还有助于采用有效的方式来处理额外的流量,例如缓存。
机器学习缓存
缓存是将结果存储到函数调用的做法,以便将来可以通过简单地检索存储的结果来更快地调用具有相同参数的此函数。缓存是加速工程流水线的常见做法,对 ML 非常有用。
缓存推理结果
一个最近最少使用 (LRU) 缓存是一种简单的缓存方法,它需要跟踪模型的最新输入及其相应的输出。在对任何新输入运行模型之前,请在缓存中查找输入。如果找到相应的条目,则直接从缓存中提供结果。图 10-6显示了此类工作流程的示例。第一行表示最初遇到输入时的缓存步骤。第二行描述了再次看到相同输入后的检索步骤。
图 10-6。图像字幕模型的缓存
这种缓存策略适用于用户将提供相同类型输入的应用程序。如果每个输入都是唯一的,这是不合适的。如果应用程序通过拍摄爪印照片来预测它们属于哪种动物,它应该很少会收到两张相同的照片,因此 LRU 缓存无济于事。
使用缓存时,您应该只缓存没有副作用的函数。例如,如果run_model
函数还将结果存储到数据库,则使用 LRU 缓存将导致不保存重复的函数调用,这可能不是预期的行为。
在 Python 中,该functools
模块提出了 LRU 缓存的默认实现,您可以将其与简单的装饰器一起使用,如下所示:
from functools import lru_cache
@lru_cache(maxsize=128)
def run_model(question_data):
# Insert any slow model inference below
pass
缓存在检索特征、处理特征时最有用,并且运行推理比访问缓存慢。根据您的缓存方法(例如,在内存中还是在磁盘上)和您使用的模型的复杂性,缓存将具有不同程度的有用性。
通过索引缓存
尽管描述的缓存方法在接收唯一输入时不合适,我们可以缓存可以预先计算的管道的其他方面。如果模型不仅仅依赖于用户输入,这是最简单的。
假设我们正在构建一个系统,允许用户搜索与他们提供的文本查询或图像相关的内容。如果我们预计查询会发生显着变化,那么缓存用户查询不太可能显着提高性能。但是,由于我们正在构建一个搜索系统,因此我们可以访问我们目录中可以返回的潜在项目列表。无论我们是在线零售商还是文档索引平台,我们都提前知道这个列表。
这意味着我们可以预先计算仅依赖于目录中项目的建模方面。如果我们选择一种允许我们提前进行此计算的建模方法,我们可以更快地进行推理。
出于这个原因,构建搜索系统时的一种常见方法是首先将所有索引文档嵌入到一个有意义的向量中(有关向量化方法的更多信息,请参阅“向量化” )。创建嵌入后,它们可以存储在数据库中。这在图 10-7的第一行中进行了说明。当用户提交搜索查询时,它会在推理时嵌入,并在数据库中执行查找以找到最相似的嵌入并返回与这些嵌入对应的产品。您可以在图 10-7的底行中看到这一点。
这种方法显着加快了推理速度,因为大部分计算都是提前完成的。嵌入已成功用于 Twitter(请参阅Twitter 博客上的这篇文章)和 Airbnb(请参阅 M. Haldar 等人的文章“Applying Deep Learning To Airbnb Search” )等公司的大规模生产管道。
图 10-7。带有缓存嵌入的搜索查询
缓存可以提高性能,但它增加了一层复杂性。缓存的大小成为一个额外的超参数,需要根据应用程序的工作负载进行调整。此外,每当更新模型或底层数据时,都需要清除缓存以防止其提供过时的结果。更一般地说,将生产中运行的模型更新到新版本通常需要小心。在下一节中,我们将介绍一些有助于简化此类更新的域。
模型和数据生命周期管理
保持最新的缓存和模型可能具有挑战性。许多模型需要定期重新训练以保持其性能水平。虽然我们将在第 11 章介绍何时重新训练您的模型,但我会喜欢简单说一下如何给用户部署更新的模型。
经过训练的模型通常存储为二进制文件,其中包含有关其类型和体系结构以及学习参数的信息。大多数生产应用程序在启动时会在内存中加载经过训练的模型并调用它来提供结果。用较新版本替换模型的一种简单方法是替换应用程序加载的二进制文件。这在图 10-8中进行了说明,其中受新模型影响的管道的唯一方面是粗体框。
然而,在实践中,这个过程通常要复杂得多。理想情况下,ML 应用程序产生可重现的结果,对模型更新具有弹性,并且足够灵活以处理重大的建模和数据处理更改。保证这一点涉及我们接下来将介绍的一些额外步骤。
图 10-8。部署同一模型的更新版本似乎是一个简单的更改
再现性
至跟踪并重现错误,您需要知道哪个模型正在生产中运行。为此,需要保留经过训练的模型和训练它们的数据集的存档。每个模型/数据集对都应分配一个唯一标识符。每次在生产中使用模型时都应记录此标识符。
在图 10-9中,我将这些要求添加到加载和保存框中,以表示这增加了 ML 管道的复杂性。
图 10-9。在保存和加载时添加重要的元数据
除了能够为现有模型的不同版本提供服务外,生产流水线还应该致力于在不造成重大停机的情况下更新模型。
弹力
启用应用程序在更新后加载新模型需要构建一个进程来加载更新的模型,理想情况下不会中断对用户的服务。这可能包括启动一个为更新模型提供服务的新服务器,并缓慢地将流量转移到它,但对于更大的系统来说,它很快就会变得更加复杂。如果新模型表现不佳,我们希望能够回滚到之前的模型。正确地完成这两项任务具有挑战性,并且传统上被归类在 DevOps 领域。虽然我们不会深入介绍这个领域,但我们将在第 11 章介绍监控。
生产变更可能比更新模型更复杂。它们可以包括对数据处理的大量更改,这些更改也应该是可部署的。
流水线灵活性
我们以前看到改进模型的最佳方法通常是通过迭代数据处理和特征生成。这意味着模型的新版本通常需要额外的预处理步骤或不同的特征。
这种变化不仅反映在模型二进制文件中,而且通常会与应用程序的新版本相关联。因此,当模型进行预测时,还应记录应用程序版本,以使该预测可重现。
这样做会给我们的流水线增加另一层复杂性,如图 10-10中添加的预处理和后处理框所示。这些现在也需要是可复制和可修改的。
部署和更新模型具有挑战性。在构建服务基础架构时,最重要的方面是能够重现模型在生产中运行的结果。这意味着将每个推理调用与运行的模型、训练模型的数据集以及为该模型提供服务的数据管道版本相关联。
图 10-10。添加模型和应用程序版本
数据处理和 DAG
至如前所述,为了产生可重现的结果,训练流水线也应该是可重现的和确定性的。对于给定的数据集、预处理步骤和模型的组合,训练流水线应该在每次训练运行时生成相同的训练模型。
构建模型需要许多连续的转换步骤,因此管道通常会在不同位置中断。这可以确保每个部分都成功运行并且它们都以正确的顺序运行。
简化这一挑战的一种方法是将我们从原始数据到训练模型的过程表示为有向无环图 (DAG),每个节点代表一个处理步骤,每个步骤代表两个节点之间的依赖关系。这个想法是数据流编程的核心,流行的 ML 库 TensorFlow 所基于的编程范例。
DAG 是一种自然的可视化方式预处理。在图 10-11中,每个箭头代表一个依赖于另一个任务的任务。表示允许我们保持每个任务的简单性,使用图形结构来表达复杂性。
图 10-11。我们应用程序的 DAG 示例
一旦我们有了 DAG,我们就可以保证我们对我们生产的每个模型都遵循相同的操作集。有多种解决方案可以为 ML 定义 DAG,包括活跃的开源项目,例如Apache Airflow或 Spotify 的Luigi。这两个包都允许您定义 DAG 并提供一组仪表板,以允许您监控 DAG 和任何相关日志的进度。
首次构建 ML 管道时,使用 DAG 可能会不必要地麻烦,但一旦模型成为生产系统的核心部分,可重复性要求使 DAG 非常引人注目。一旦模型被定期重新训练和部署,任何有助于系统化、调试和版本化管道的工具都将成为节省时间的关键。
寻求反馈
这个本章涵盖的系统有助于确保我们及时为每位用户提供准确的结果。为了保证结果的质量,我们介绍了检测模型预测是否不准确的策略。我们为什么不问用户?
你可以通过明确要求反馈和测量隐式信号来收集用户的反馈。您可以在显示模型的预测时要求明确的反馈,并附上一种供用户判断和纠正预测的方法。这可以像询问“这个预测有用吗?”的对话一样简单。或者更微妙的东西。
例如,预算应用程序 Mint 会自动对帐户上的每笔交易进行分类(类别包括Travel、Food等)。如图 10-12所示,每个类别在 UI 中显示为一个字段,用户可以根据需要进行编辑和更正。例如,此类系统允许收集有价值的反馈,以比满意度调查更少侵入性的方式不断改进模型。
图 10-12。让用户直接修复错误
用户无法为模型所做的每个预测提供反馈,因此收集隐式反馈是判断 ML 性能的重要方法。收集此类反馈包括查看用户执行的操作以推断模型是否提供了有用的结果。
隐式信号很有用,但更难解释。您不应该希望找到一个总是与模型质量相关的隐式信号,而只能是一个整体相关的信号。例如,在推荐系统中,如果用户点击推荐的项目,您可以合理地假设该推荐是有效的。这并非在所有情况下都是正确的(人们有时会点击错误的东西!),但只要它经常是正确的,它就是一个合理的隐含信号。
通过收集这些信息,如图 10-13所示,您可以估计用户发现结果有用的频率。收集此类隐式信号很有用,但会增加收集和存储此数据的风险,并可能引入我们在第 8 章中讨论的负反馈循环。
图 10-13。作为反馈来源的用户操作
在你的产品中建立隐式反馈机制可能是收集额外数据的一种有价值的方式。许多动作可以被认为是隐式和显式反馈的混合。
假设我们在 ML 编辑器的推荐中添加了一个“在 Stack Overflow 上提问”按钮。通过分析哪些预测导致用户点击此按钮,我们可以衡量足以作为问题发布的推荐的比例。通过添加这个按钮,我们并不是直接询问用户这个建议是否好,而是允许他们根据它采取行动,从而给我们一个“弱标签”(有关弱标签数据的提醒,请参阅“数据类型” )问质量。
除了作为训练数据的良好来源之外,隐式和显式用户反馈可能是注意到 ML 产品性能下降的第一种方式。虽然理想情况下,应该在向用户显示之前发现错误,但监视此类反馈有助于更快地检测和修复错误。我们将在第 11 章中更详细地介绍这一点。
部署和更新模型的策略因团队规模和他们的 ML 经验而异。本章中的一些解决方案对于 ML 编辑器等原型来说过于复杂。另一方面,一些在 ML 上投入了大量资源的团队构建了复杂的系统,使他们能够简化部署过程并保证为用户提供高质量的服务。接下来,我将分享对 Chris Moody 的采访,他是 Stitch Fix 的 AI Instruments 团队的负责人,他将向我们介绍他们在部署 ML 模型时的理念。
Chris Moody:授权数据科学家部署模型
克里斯穆迪来自加州理工学院和 UCSC 的物理学背景,现在领导 Stitch Fix 的 AI Instruments 团队。他对 NLP 有着浓厚的兴趣,并涉足深度学习、变分方法和高斯过程。他为Chainer深度学习库做出了贡献,为scikit-learn的超快速 Barnes–Hut 版本的 t-SNE 做出了贡献,并用Python编写了(为数不多的!)稀疏张量分解库。他还构建了自己的 NLP 模型lda2vec。
问:数据科学家在 Stitch Fix 研究模型生命周期的哪一部分?
答:在 Stitch Fix,数据科学家拥有整个建模流程。这个管道很广泛,包括构思、原型制作、设计和调试、ETL 以及使用 scikit-learn、pytorch 和 R 等语言和框架进行的模型训练。此外,数据科学家负责建立系统以衡量指标并为他们的模型建立“健全性检查”。最后,数据科学家运行 A/B 测试,监控错误和日志,并根据他们观察到的情况根据需要重新部署更新的模型版本。为了能够做到这一点,他们利用了平台和工程团队所做的工作。
问:平台团队如何使数据科学工作更轻松?
答:平台团队工程师的目标是找到正确的建模抽象。这意味着他们需要了解数据科学家的工作方式。工程师不会为从事给定项目的数据科学家构建单独的数据管道。他们构建解决方案,使数据科学家能够自己这样做。更一般地说,他们构建工具以使数据科学家能够拥有整个工作流程。这使工程师能够花更多时间改进平台,减少构建一次性解决方案的时间。
Q:你如何判断模型部署后的性能?
答:Stitch Fix 的很大一部分优势在于让人类和算法协同工作。例如,Stitch Fix 花费大量时间思考向其造型师呈现信息的正确方式。从根本上说,如果您有一个 API,一方面公开您的模型,另一方面公开用户(例如造型师或商品买家),您应该如何设计他们之间的交互?
乍一看,您可能很想构建一个前端来简单地向用户展示您的算法结果。不幸的是,这会让用户觉得他们无法控制算法和整个系统,并且当它表现不佳时会导致沮丧。相反,您应该将此交互视为一个反馈循环,允许用户更正和调整结果。这样做可以让用户训练算法,并通过提供反馈对整个过程产生更大的影响。此外,这使您可以收集标记数据来判断模型的性能。
为了做好这一点,数据科学家应该问问自己,他们如何才能将模型暴露给用户,以便让他们的工作更轻松,并使他们能够使模型变得更好。这意味着,由于数据科学家最清楚什么样的反馈对他们的模型最有用,因此他们拥有端到端的流程是不可或缺的。他们可以发现任何错误,因为他们可以看到整个反馈循环。
问:您如何监控和调试模型?
答:当您的工程团队构建出出色的工具时,监控和调试就会变得容易得多。Stitch Fix 构建了一个内部工具,该工具接受建模管道并创建 Docker 容器、验证参数和返回类型、将推理管道公开为 API、将其部署在我们的基础设施上,并在其上构建仪表板。该工具允许数据科学家直接修复部署期间或之后发生的任何错误。由于数据科学家现在负责对模型进行故障排除,我们还发现这种设置会激励简单而强大的模型,这些模型往往很少出现故障。整个管道的所有权导致个人优化影响和可靠性,而不是模型复杂性。
问:你们如何部署新的模型版本?
答:此外,数据科学家使用定制的 A/B 测试服务运行实验,该服务允许他们定义粒度参数。然后他们分析测试结果,如果团队认为他们是决定性的,他们就会自己部署新版本。
在部署方面,我们使用类似于金丝雀开发的系统,我们首先将新版本部署到一个实例,然后在监控性能的同时逐步更新实例。数据科学家可以访问一个仪表板,该仪表板显示每个版本下的实例数量以及部署过程中的连续性能指标。
结论
在本章中,我们介绍了通过主动检测模型的潜在故障并找到减轻故障的方法来使我们的响应更具弹性的方法。这包括确定性验证策略和过滤模型的使用。我们还介绍了使生产模型保持最新所带来的一些挑战。然后,我们讨论了一些可以估计模型性能的方法。最后,我们查看了一个经常大规模部署 ML 的公司的实际示例,以及他们为此构建的流程。
在第 11 章中,我们将介绍其他方法来关注模型的性能并利用各种指标来诊断 ML 驱动的应用程序的健康状况。