原文:
zh.annas-archive.org/md5/e14f18bbb2088c66db1babf46e8b6eee
译者:飞龙
第七章:揭示深度学习模型的秘密
到目前为止,我们已经描述了如何构建并高效训练深度学习(DL)模型。然而,模型训练通常涉及多次迭代,因为针对特定任务正确配置训练的粗略指导仅存。
在本章中,我们将介绍超参数调优,这是寻找正确训练配置的最标准过程。随着我们指导您完成超参数调优的步骤,我们将介绍用于调优过程的流行搜索算法(网格搜索、随机搜索和贝叶斯优化)。我们还将探讨可解释 AI 领域,即在预测过程中理解模型操作的过程。我们将描述这一领域中的三种最常见技术:置换特征重要性(PFI)、SHapley 加法解释(SHAP)、局部可解释模型无关解释(LIME)。
本章中,我们将涵盖以下主要主题:
-
使用超参数调优获取最佳性能模型
-
通过可解释 AI 理解模型行为
技术要求
您可以从本书的 GitHub 存储库下载本章的补充材料,链接为 github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_7
。
使用超参数调优获取最佳性能模型
如在 第三章 中描述的,开发强大的深度学习模型,获取能够为底层任务提取正确模式的 DL 模型需要适当配置多个组件。尽管构建合适的模型架构通常会引入许多困难,但设置适当的模型训练是大多数人都在努力解决的另一个挑战。
在机器学习(ML)中,超参数 是指控制学习过程的任何参数。在许多情况下,数据科学家通常关注与模型相关的超参数,例如特定类型层的数量、学习率或优化器类型。然而,超参数还包括数据相关配置,如应用的增强类型和模型训练的抽样策略。将一组超参数进行更改并理解性能变化的迭代过程,以找到目标任务的正确超参数集合,称为超参数调优。确切地说,您将有一组要探索的超参数。对于每次迭代,将会以不同的设置配置一个或多个超参数,并使用调整后的设置训练新模型。经过迭代过程,用于最佳模型的超参数配置将是最终输出。
在本章中,我们将学习用于超参数调整的各种技术和工具。
超参数调整技术
超参数调整技术可以通过选择目标超参数的值的方式而异。在各种技术中,我们将重点介绍最常见的几种:网格搜索、随机搜索和贝叶斯优化。
网格搜索
最基本的方法被称为网格搜索,其中每个可能的值都逐个评估。例如,如果你想探索从 0 到 1 的学习率,增加幅度为 0.25,那么网格搜索将会为每个可能的学习率(0.25、0.5、0.75 和 1)训练模型,并选择生成最佳模型的学习率。
随机搜索
另一方面,随机搜索生成超参数的随机值,并重复训练,直到达到最大实验次数为止。如果我们将前面章节的例子转换为随机搜索,我们必须定义最大实验次数和学习率的边界。在这个例子中,我们将最大实验次数设置为 5,边界设置为 0 到 1。然后,随机搜索将在 0 到 1 之间选择一个随机值,并使用所选的学习率训练模型。这个过程将重复 5 次,选择生成最佳模型的学习率作为超参数调整的输出。
为了帮助理解,以下图表总结了网格搜索和随机搜索之间的区别:
图 7.1 – 网格搜索和随机搜索的差异
在上图中,x和y表示两个不同的超参数。每个轴上的紫色和绿色图表显示了模型性能随每个超参数变化的情况。
虽然网格搜索和随机搜索都很容易实现,但它们都有一个共同的限制:它们不能保证目标超参数的最佳值。这个问题主要源于在选择下一个要探索的值时没有考虑前面的结果。为了克服这个问题,引入了一种新的搜索算法:贝叶斯优化。
贝叶斯优化
贝叶斯优化的理念很简单:构建一个代理模型,映射超参数与底层模型之间的关系,并在整个超参数调整过程中进行调整,以便我们可以选择一个超参数值,这个超参数值很可能会在后续实验中使我们对关系有更好的理解。利用生成的代理模型,我们可以选择很可能会给我们带来更好模型的超参数值。
有许多构建代理模型的方法。如果我们假设关系可以表示为线性函数,那么代理模型生成过程将简单地是线性回归。实际上,关系要复杂得多,最成功的技术是使用高斯过程回归。在这里,我们假设关系可以用一组正态分布来表示。换句话说,我们选择的每个值都是从多元正态分布中随机选择的。如果我们想详细讨论贝叶斯优化的每个细节,我们将需要引入多个概率和数学术语。我们相信,本节的高层描述和下一节的完整示例足以让您应用使用贝叶斯优化进行超参数调整。如果您想了解贝叶斯优化背后的理论,请访问ieeexplore.ieee.org/abstract/document/7352306
。
超参数调整工具
由于超参数调整在 ML 项目中起着重要作用,因此存在许多旨在简化此过程的库。以下是一些流行的库:
-
Scikit-Optimize:
scikit-optimize.github.io
-
Optuna:
optuna.org
-
HyperOpt:
hyperopt.github.io
-
Ray Tune:
docs.ray.io/en/latest/tune/index.html
-
Bayesian Optimization:
github.com/fmfn/BayesianOptimization
-
Metric Optimization Engine (MOE):
github.com/Yelp/MOE
-
Spearmint:
github.com/HIPS/Spearmint
-
GPyOpt:
github.com/SheffieldML/GPyOpt
-
SigOpt:
sigopt.com
-
FLAML:
github.com/microsoft/FLAML
-
Dragonfly:
github.com/dragonfly/dragonfly
-
HpBandSter:
github.com/automl/HpBandSter
-
Nevergrad:
github.com/facebookresearch/nevergrad
-
ZOOpt:
github.com/polixir/ZOOpt
-
SageMaker:
docs.aws.amazon.com/sagemaker/latest/dg/automatic-model-tuning-how-it-works.html
在各种工具中,我们将关注 Ray Tune,因为我们在《第六章》Chapter 6 Efficient Model Training 的 Training a model using Ray 部分中介绍了如何使用 Ray 进行分布式训练。
使用 Ray Tune 进行超参数调整
作为 Ray 的一部分,一个旨在跨多台机器扩展 Python 工作负载的框架,Ray Tune 专为大规模实验执行和超参数调整而设计。在本节中,我们将为您介绍如何使用 Ray Tune 进行超参数调整的配置和调度。尽管示例旨在抽象表示模型训练功能,但 Ray Tune 的设置和文档足够清晰,以至于 PyTorch 和 TensorFlow(TF)的集成自然而然地在本节末尾完成。
首先,我们将介绍 Ray Tune 的基础知识。Ray Tune 的核心功能来自 tune.run
函数,该函数管理所有实验、日志和检查点。tune.run
函数的基本用法如下代码片段所示:
from ray import tune
def tr_function(conf):
num_iterations = conf["num_it"]
for i in range(num_iterations):
… // training logic
tune.report(mean_accuracy=acc)
tune.run(
run_or_experiment=tr_function
conf={"num_it": tune.grid_search([10, 20, 30, 40])})
tune.run
函数接受 run_or_experiment
参数,该参数定义训练逻辑,以及 conf
参数,该参数配置超参数调整。每个超参数在 conf
中提供的搜索函数类型决定了实验的数量。在上述示例中,我们使用了 tune.grid_search([10, 20, 30, 40])
,这将启动四个实验,每个实验都会运行为 num_iterations
分配的函数 (tr_function
),并使用不同的 num_iterations
值。在 tr_function
中,我们可以通过 conf
参数访问分配的超参数。值得一提的是,Ray Tune 提供了大量的采样方法 (docs.ray.io/en/latest/tune/api_docs/search_space.html#tune-sample-docs
)。
Ray Tune 作为 tune.suggest
的一部分整合了许多开源优化库,为超参数调整提供各种先进的搜索算法。流行的算法包括 HyperOpt、Bayesian Optimization、Scitkit-Optimize 和 Optuna。完整列表可以在 docs.ray.io/en/latest/tune/api_docs/suggestion.html
找到。在下面的示例中,我们将描述如何使用 BayesOptSearch
,正如其名称所示,它实现了贝叶斯优化:
from ray import tune
from ray.tune.suggest.bayesopt import BayesOptSearch
conf = {"num_it": tune.randint(100, 200)}
bayesopt = BayesOptSearch(metric="mean_accuracy", mode="max")
tune.run(
run_or_experiment=tr_function
config = conf,
search_alg = bayesopt)
在上述代码片段中,我们向 search_alg
参数提供了 BayesOptSearch
的一个实例。该示例将尝试找到 num_iterations
,以提供具有最高 mean_accuracy
的模型。
另一个tune.run
的关键参数是stop
。该参数可以接受一个字典、一个函数或一个Stopper
对象,用于定义停止标准。如果是一个字典,键必须是run_or_experiment
函数返回结果中的字段之一。如果是一个函数,它应返回一个布尔值,一旦满足停止条件就为True
。以下是这两种情况的示例:
# dictionary-based stop
tune.run(tr_function,
stop={"training_iteration": 20,
"mean_accuracy": 0.96})
# function-based stop
def stp_function(trial_id, result):
return result["training_iteration"] > 20 or
result["mean_accuracy"] > 0.96
tune.run(tr_function, stop=stp_function)
在基于字典的示例中,每个试验将在完成 10 次迭代或mean_accuracy
达到指定值0.96
时停止。基于函数的示例实现了相同的逻辑,但使用了stp_function
函数。对于stopper
类用例,您可以参考docs.ray.io/en/latest/tune/tutorials/tune-stopping.html#stopping-with-a-class
。
试验是 Ray Tune 的内部数据结构,包含关于每个实验的元数据 (docs.ray.io/en/latest/tune/api_docs/internals.html#trial-objects
)。每个试验都有一个唯一的 ID(trial.trial_id
),其超参数设置可以通过trial.config
进行检查。有趣的是,可以通过tune.run
的resources_per_trial
参数和trial.placement_group_factory
为每个试验分配不同规模的机器资源。此外,num_samples
参数可用于控制试验的数量。
从ray.tune
返回的Analysis
实例可以获得您实验的摘要。以下代码片段描述了您可以从Analysis
实例中检索的一组信息:
# last reported results
df = analysis.results_df
# list of trials
trs = analysis.trials
# max accuracy
max_acc_df = analysis.dataframe(metric="mean_accuracy", mode="max")
# dict mapping for all trials in the experiment
all_dfs = analysis.trial_dataframes
您还可以从Analysis
实例中检索其他有用的信息。完整详情请参阅docs.ray.io/en/latest/tune/api_docs/analysis.html
。
这完成了 Ray Tune 的核心组件。如果您希望为 PyTorch 或 TF 模型训练集成 Ray Tune,您所需做的就是调整示例中的tr_function
,使其在记录相关性能指标的同时训练您的模型。
总体而言,我们已探讨了超参数调整的不同选项。本节介绍的工具应有助于我们有效地找到 DL 模型的最佳配置。
需要记住的事情
a. 获得特定任务的工作 DL 模型需要找到合适的模型架构并使用适当的训练配置。找到最佳组合的过程称为超参数调整。
b. 三种最流行的超参数调整技术是网格搜索、随机搜索和贝叶斯优化。
c. 流行的超参数调整工具包括 Scikit-Optimize、Optuna、Hyperopt、Ray Tune、Bayesian Optimization、MOE、Spearmint、GpyOpt 和 SigOpt。
到目前为止,我们将 DL 模型视为黑匣子。超参数调优涉及搜索未知空间,无法解释模型如何找到潜在模式。在下一节中,我们将探讨研究人员最近致力于理解 DL 灵活性的工作。
通过可解释人工智能理解模型的行为
可解释人工智能是一个非常活跃的研究领域。在商业环境中,理解 AI 模型往往能够轻松带来显著的竞争优势。所谓的黑匣子模型(复杂算法模型),尽管能够带来出色的结果,却因其隐含逻辑而常遭批评。高层管理人员很难完全基于 AI 设计核心业务,因为解释模型和预测并不是一件容易的事情。您如何说服您的业务合作伙伴,表明 AI 模型将始终产生预期结果?您如何确保模型在新数据上仍然有效?模型是如何生成结果的?可解释人工智能帮助我们解答这些问题。
在我们进一步探讨之前,让我们先了解两个重要的概念:可解释性和解释性。乍一听可能很相似。可解释性告诉我们为什么特定的输入会产生特定模型的输出:特定变量对结果的影响。解释性超越了可解释性;它不仅关注输入和输出之间的因果关系,还帮助我们理解模型作为一个整体的工作方式,包括其所有子元素。解释性还由透明性、可复现性和可转移性三个基本理念驱动。这意味着我们应该能够完全理解我们的模型所做的事情,数据如何在经过模型时影响模型,并能够重现结果。
可解释人工智能在每个 ML 项目的各个步骤中发挥作用——开发(模型架构的解释和每个超参数的含义)、训练(训练过程中模型的变化)、以及推理(结果解释)。在 DL 模型的情况下,由于网络架构复杂、算法复杂性高,以及在初始化权重、偏置、正则化和超参数优化时使用随机数,因此实现可解释性是困难的。
在本节中,我们将讨论几种常用于增强 DL 模型可信度的方法:置换特征重要性(PFI)、特征重要性(FI)、SHapley 加性解释(SHAP)和局部可解释模型无关解释(LIME)。所有这些方法都是模型无关的;它们既可应用于 DL 模型,也可应用于其他支持 ML 模型,常用于设定基线评估指标。
置换特征重要性
神经网络缺乏理解输入特征对预测(模型输出)影响所需的内在属性。然而,有一种称为置换特征重要性(PFI)的模型无关方法可以解决这一困难。*PFI 的思想来自于输入特征与输出之间的关系:对于与输出变量高度相关的输入特征,改变其值会增加模型的预测误差。*如果关系较弱,模型的性能不会受到太多影响。如果关系很强,性能将下降。PFI 经常应用于测试集,以获取对未见数据上模型可解释性的更广泛理解。
PFI 的主要缺点与数据具有一组相关输入特征相关。在这种情况下,即使您改变该组的一个特征,模型的性能也不会改变太多,因为其他特征将保持不变。
深入探讨这个想法,我们可以完全删除该特征并测量模型的性能。这种方法称为特征重要性(FI),也称为置换重要性(PI)或平均减少精度(MDA)。让我们看看如何为任何黑盒模型实现 FI。
特征重要性
在本节中,我们将使用ELI5 Python 包(eli5.readthedocs.io
)执行 FI 分析。它在 FI 领域突出的原因是非常简单易用。让我们看一下在 Keras 定义的 TF 模型中使用 ELI5 的最小代码示例(有关模型定义的详细信息,请参见第三章,开发一个强大的深度学习模型):
import eli5
from eli5.sklearn import PermutationImportance
def score(self, x, y_true):
y_pred = model.predict(x)
return tf.math.sqrt( tf.math.reduce_mean( tf.math.square(y_pred-y_true), axis=-1))
perm = PermutationImportance(model, random_state=1, scoring=score).fit(features, labels)
fi_perm=perm.feature_importances_
fi_std=perm.feature_importances_std_
正如您所见,代码几乎是自解释的。首先,我们需要为计算目标评估指标的评分函数创建一个包装器。然后,将tf.keras
模型传递给PermutationImportance
类的构造函数。fit
函数负责处理特征和标签的特征重要性计算。计算完成后,我们可以访问每个特征的平均特征重要性(fi_perm
)和置换结果的标准差(fi_std
)。以下代码片段展示了如何将置换重要性的结果可视化为条形图:
plt.figure()
for index, row in enumerate(fi_perm):
plt.bar(index,
fi_perm[index],
color="b",
yerr=fi_std[index],
align="center")
plt.show()
如果模型既不基于 scikit-learn 也不基于 Keras,则需要使用permutation_importance.get_score_importance
函数。以下代码片段描述了如何在 PyTorch 模型中使用该函数:
import numpy as np
from eli5.permutation_importance import get_score_importances
# A trained PyTorch model
black_box_model = ...
def score(X, y):
y_pred = black_box_model.predict(X)
return accuracy_score(y, y_pred)
base_score, score_decreases = get_score_importances(score, X, y)
feature_importances = np.mean(score_decreases, axis=0)
与PermutationImportance
类不同,get_score_importances
函数同时接收评分函数、特征和标签。
接下来,我们将看看SHapley Additive exPlanations(SHAP),这也是一种与模型无关的方法。
SHapley Additive exPlanations(SHAP)
SHAP 是一种解释方法,利用夏普利值来理解给定黑盒模型。我们不会涵盖 SHAP 基于的合作博弈理论,但我们将以高层次来讨论过程。首先,让我们看看 Shapley 值的定义:在不同模拟中,所有可能联盟的平均边际贡献。这究竟意味着什么?假设有四位朋友(f1、f2、f3和f4)共同努力为一个在线游戏获得最高分数。要计算一个人的夏普利值,我们需要计算边际贡献,即该人参与游戏与不参与游戏时分数的差异。这个计算必须针对所有可能的子组(联盟)进行。
让我们仔细看一看。为了计算f1对朋友f2、f3和f4联合体的边际贡献,我们需要执行以下操作:
-
计算所有朋友(f1、f2、f3和f4)生成的分数(s1)。
-
计算朋友f2、f3和f4生成的分数(s2)。
-
最后,朋友f1对朋友f2、f3和f4联合体的边际贡献*(v)等于s1-s2*。
现在,我们需要计算所有子组合的边际贡献(不仅仅是朋友的联合体;即f2、f3和f4)。这里是每一个可能的组合:
-
f1与无人正在贡献(v1)
-
f1和f2与f2(v2)
-
f1和f3与f3(v3)
-
f1和f4与f4(v4)
-
f1和f2和f3与f2和f3(v5)
-
f1和f2和f4与f2和f4(v6)
-
f1和f3和f4与f3和f4(v7)
-
f1和f2和f3和f4与f2和f3和f4(v8)
总体而言,f1的夏普利值(SV)为*(v1+v2+…+v8) / 8*。
为了使我们的结果具有统计学意义,我们需要在多次模拟中运行这些计算。您可以看到,如果我们扩展朋友的数量,计算将变得非常复杂,导致计算资源的高消耗。因此,我们使用具体的近似值,产生了不同类型的所谓解释器(夏普利值的近似器),这些解释器可以在shap
库中找到(shap.readthedocs.io/en/latest/index.html
)。通过比较所有朋友的夏普利值,我们可以找出每个个体对最终分数的贡献。
如果我们回到 DL 模型的解释,我们可以看到朋友们变成了一组特征,而分数则是模型的性能。有了这个理念,让我们来看看 SHAP 解释器,它可以用于 DL 模型:
-
KernelExplainer
:这是最流行的方法,也是与模型无关的。它基于局部可解释模型无关解释(LIME),我们将在下一节讨论。 -
DeepExplainer
:这种方法基于 DeepList 方法,它在特定输入上分解输出(arxiv.org/abs/1704.02685
)。 -
GradientExplainer
:这种方法基于集成梯度的扩展(arxiv.org/abs/1703.01365
)。
例如,我们将展示一个应用于 TF 模型的极简代码示例。完整详情请参阅官方文档 shap-lrjball.readthedocs.io/en/latest/index.html
:
import shap
# initialize visualization
shap.initjs()
model = … # tf.keras model or PyTorch model (nn.Module)
explainer = shap.KernelExplainer(model, sampled_data)
shap_values = explainer.shap_values(data, nsamples=300)
shap.force_plot(explainer.expected_value, shap_values, data)
shap.summary_plot(shap_values, sampled_data, feature_names=names, plot_type="bar")
对于 PyTorch 模型,您需要将模型包装在一个包装器中,以将输入和输出转换为正确的类型(f=lambda x: model(torch.autograd.Variable(torch.from_numpy(x))).detach().numpy()
)。在下面的示例中,我们定义了 KernelExplainer
,它接受 DL 模型和 sampled_data
作为输入。接下来,我们使用 explainer.shap_values
函数计算 SHAP 值(Shapley 值的近似值)。在本例中,我们使用 300
个扰动样本来估计给定预测的 SHAP 值。如果我们的 sampled_data
包含 100
个示例,则将执行 100*300 次模型评估。类似地,您可以使用 GradientExplainer
(shap.GradientExplainer(model, sampled_data)
)或 DeepExplainer
(shap.DeepExplainer(model, sampled_data)
)。sampled_data
的大小需要足够大,以正确表示分布。在最后几行中,我们使用 shap.force_plot
函数在加性力布局中可视化 SHAP 值,并使用 shap.summary_plot
函数创建全局模型解释图。
现在,让我们看看 LIME 方法。
本地可解释的模型无关解释(LIME)
LIME 是一种训练本地替代模型以解释模型预测的方法。首先,您需要准备一个要解释的模型和一个样本。LIME 使用您的模型从一组扰动数据中收集预测,并将它们与原始样本进行比较,以分配相似性权重(如果预测接近初始样本的预测,则权重较高)。LIME 使用特定数量的特征通过相似性权重在采样数据上拟合一个内在可解释的替代模型。最后,LIME 将替代模型解释视为所选示例的黑盒模型的解释。要执行 LIME 分析,我们可以使用 lime
包(lime-ml.readthedocs.io
)。
让我们看一个为 DL 模型设计的示例:
from lime.lime_tabular import LimeTabularExplainer as Lime
from matplotlib import pyplot as plt
expl = Lime(features, mode='classification', class_names=[0, 1])
# explain first sample
exp = expl.explain_instance(x[0], model.predict, num_features=5, top_labels=1)
# show plot
exp.show_in_notebook(show_table=True, show_all=False)
在前面的示例中,我们正在使用 LimeTabularExplainer
类。构造函数接受一个训练集、特征、类名和模式类型 ('classification'
)。同样,您可以通过提供 'regression'
模式类型来设置用于回归问题的 LIME。然后,通过展示五个最重要的特征及其影响,我们解释了测试集的第一个预测 (x[0]
)。最后,我们生成了一个基于计算的 LIME 解释的图表。
要记住的事项
a. 在解释性人工智能中,模型可解释性和解释性是两个关键概念。
b. 解释性人工智能中流行的无关模型技术包括 PFI、FI、SHAP 和 LIME。
c. PFI、FI 和 SHAP 是允许您在本地(单个样本)和全局(一组样本)级别解释您的模型的方法。另一方面,LIME 关注单个样本及其对应的模型预测。
在本节中,我们已经解释了解释性人工智能的概念以及四种最常见的技术:PFI、FI、SHAP 和 LIME。
总结
我们从调整超参数开始了本章。我们描述了用于超参数调整的三种基本搜索算法(网格搜索、随机搜索和贝叶斯优化),并介绍了许多可集成到项目中的工具。在列出的工具中,我们涵盖了 Ray Tune,因为它支持分布式超参数调整,并实现了许多即用的最先进搜索算法。
然后,我们讨论了解释性人工智能。我们解释了最标准的技术(PFI、FI、SHAP 和 LIME),以及它们如何用来发现模型在数据集中每个特征变化时的行为变化。
在下一章中,我们将把焦点转向部署。我们将学习 ONNX,这是一种用于机器学习模型的开放格式,并了解如何将 TF 或 PyTorch 模型转换为 ONNX 模型。
第三部分:部署与维护
在部署深度学习项目过程中经常会面临许多复杂挑战。在许多情况下,部署环境与开发环境不同,这种差异可能会引入各种限制。在本部分中,我们介绍工程师经常遇到的常见问题,并针对每个挑战分享有效的解决方案。在最后一章中,我们描述了深度学习项目的最后阶段,包括评估项目并讨论未来项目的潜在改进。
本部分包括以下章节:
-
第八章, 简化深度学习模型部署
-
第九章, 扩展深度学习管道
-
第十章, 提升推理效率
-
第十一章, 移动设备上的深度学习
-
第十二章, 监控生产中的深度学习端点
-
第十三章, 审查完成的深度学习项目
第八章:简化深度学习模型部署
在生产环境中部署的 深度学习 (DL) 模型通常与训练过程中的模型有所不同。它们通常被增强以处理传入请求,并具有最高性能。然而,目标环境通常过于广泛,因此需要大量定制以涵盖非常不同的部署设置。为了克服这一困难,您可以利用 开放神经网络交换 (ONNX),这是一种用于 ML 模型的标准文件格式。在本章中,我们将介绍如何利用 ONNX 在 DL 框架之间转换 DL 模型,并如何将模型开发过程与部署分离。
在本章中,我们将涵盖以下主要内容:
-
ONNX 简介
-
TensorFlow 和 ONNX 之间的转换
-
PyTorch 和 ONNX 之间的转换
技术要求
您可以从以下 GitHub 链接下载本章的补充材料:github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_8
。
ONNX 简介
您可以使用多种 DL 框架来训练 DL 模型。然而,DL 模型部署中的一个主要困难是这些框架之间缺乏互操作性。例如,PyTorch 和 TensorFlow (TF) 之间的转换引入了许多困难。
在许多情况下,DL 模型还会为部署环境进一步增强,以提高准确性并减少推断延迟,利用底层硬件提供的加速功能。不幸的是,这需要广泛的软件和硬件知识,因为每种类型的硬件为运行应用程序提供不同的加速。用于 DL 的常用硬件包括 中央处理单元 (CPU)、图形处理单元 (GPU)、关联处理单元 (APU)、张量处理单元 (TPU)、现场可编程门阵列 (FPGA)、视觉处理单元 (VPU)、神经处理单元 (NPU) 和 JetsonBoard。
此过程不是一次性操作;一旦模型以任何方式更新,可能需要重复此过程。为了减少这一领域的工程工作量,一组工程师共同努力,提出了一种标准化模型组件的中介:.onnx
文件,用于跟踪模型设计及网络内每个操作如何与其他组件链接。.onnx
文件 (github.com/lutzroeder/netron
)。以下是一个示例可视化:
图 8.1 – ONNX 文件的 Netron 可视化
如您所见,ONNX 是训练框架和部署环境之间的一层。虽然 ONNX 文件定义了一种交换格式,但也存在支持 ONNX 模型的 ONNX Runtime (ORT),后者支持对 ONNX 模型进行硬件无关的加速优化。换句话说,ONNX 生态系统允许您选择任何 DL 框架进行训练,并使得部署时的硬件特定优化变得轻而易举。
图 8.2 – ONNX 在深度学习项目中的位置
总结一下,ONNX 有助于以下任务:
-
简化不同深度学习框架之间的模型转换
-
为深度学习模型提供与硬件无关的优化
在接下来的部分中,我们将更详细地了解 ORT。
使用 ONNX Runtime 运行推理
ORT 旨在直接支持使用 ONNX 模型进行训练和推理,无需将其转换为特定框架。然而,训练并不是 ORT 的主要用例,因此我们将专注于推理这一方面,在本节中进行讨论。
ORT 利用不同的硬件加速库,称为 Execution Providers (EPs),以提高各种硬件架构的延迟和准确性。无论模型训练期间使用的 DL 框架和底层硬件如何,ORT 推理代码保持不变。
下面的代码片段是一个 ONNX 推理代码示例。完整详情请查阅 onnxruntime.ai/docs/get-started/with-python.html
。
import onnxruntime as rt
providers = ['CPUExecutionProvider'] # select desired provider or use rt.get_available_providers()
model = rt.InferenceSession("model.onnx", providers=providers)
onnx_pred = model.run(output_names, {"input": x}) # x is your model's input
InferenceSession
类接受文件名、序列化的 ONNX 模型或 ORT 模型的字节字符串作为输入。在上述示例中,我们指定了一个 ONNX 文件的名称 ("model.onnx"
)。providers
参数和按优先顺序排列的执行提供者列表(如 CPUExecutionProvider
、TvmExecutionProvider
、CUDAExecutionProvider
等)是可选的,但非常重要,因为它们定义了将应用的硬件加速类型。在最后一行,run
函数触发模型预测。run
函数有两个主要参数:output_names
(模型输出的名称)和 input_feed
(输入字典,包含您希望使用模型进行预测的输入名称和值)。
需要记住的事项
a. ONNX 提供了用于 ML 模型的标准化和跨平台的表示。
b. ONNX 可以用于将一个 DL 框架中实现的模型转换为另一个框架,转换过程需要很少的工作量。
c. ORT 为已部署的模型提供与硬件无关的加速。
在接下来的两节中,我们将看看使用 TF 和 PyTorch 创建 ONNX 模型的过程。
在 TensorFlow 和 ONNX 之间的转换
首先,我们将研究 TF 到 ONNX 的转换。我们将这个过程分解为两步:将 TF 模型转换为 ONNX 模型,以及将 ONNX 模型转换回 TF 模型。
将 TensorFlow 模型转换为 ONNX 模型
tf2onnx
用于将 TF 模型转换为 ONNX 模型 (github.com/onnx/tensorflow-onnx
)。此库支持 TF 的两个版本(版本 1 和版本 2)。此外,还支持将模型转换为特定部署的 TF 格式,如 TensorFlow.js 和 TensorFlow Lite。
要将使用 saved_model
模块生成的 TF 模型转换为 ONNX 模型,可以使用 tf2onnx.convert
模块,如下所示:
python -m tf2onnx.convert --saved-model tensorflow_model_path --opset 9 --output model.onnx
在上述命令中,tensorflow-model-path
指向磁盘上保存的 TF 模型,--output
定义了生成的 ONNX 模型保存的位置,--opset
设置了 ONNX 的 opset
,它定义了 ONNX 的版本和操作符 (github.com/onnx/onnx/releases
)。如果您的 TF 模型未使用 tf.saved_model.save
函数保存,需要按照以下格式指定输入和输出格式:
# model in checkpoint format
python -m tf2onnx.convert --checkpoint tensorflow-model-meta-file-path --output model.onnx --inputs input0:0,input1:0 --outputs output0:0
# model in graphdef format
python -m tf2onnx.convert --graphdef tensorflow_model_graphdef-file --output model.onnx --inputs input0:0,input1:0 --outputs output0:0
上述命令描述了 Checkpoint (www.tensorflow.org/api_docs/python/tf/train/Checkpoint
) 和 GraphDef (www.tensorflow.org/api_docs/python/tf/compat/v1/GraphDef
) 格式模型的转换。关键参数是 --checkpoint
和 --graphdef
,它们指示了模型格式以及源模型的位置。
tf2onnx
还提供了一个 Python API,您可以在 github.com/onnx/tensorflow-onnx
找到它。
接下来,我们将看一下如何将 ONNX 模型转换为 TF 模型。
将 ONNX 模型转换为 TensorFlow 模型
虽然 tf2onnx
用于从 TF 到 ONNX 的转换,但 onnx-tensorflow
(github.com/onnx/onnx-tensorflow
) 用于将 ONNX 模型转换为 TF 模型。它与 tf2onnx
一样,基于终端命令。以下是一个简单的 onnx-tf
命令示例:
onnx-tf convert -i model.onnx -o tensorflow_model_file
在上述命令中,-i
参数用于指定源 .onnx
文件,而 -o
参数用于指定新 TF 模型的输出位置。onnx-tf
命令的其他用例在 github.com/onnx/onnx-tensorflow/blob/main/doc/CLI.md
中有详细说明。
此外,您也可以使用 Python API 执行相同的转换:
import onnx
from onnx_tf.backend import prepare
onnx_model = onnx.load("model.onnx")
tf_rep = prepare(onnx_model)
tensorflow-model-file-path = path/to/tensorflow-model
tf_rep.export_graph(tensorflow_model_file_path)
在上述 Python 代码中,使用 onnx.load
函数加载 ONNX 模型,然后使用从 onnx_tf.backend
导入的 prepare
进行调整,最后使用 export_graph
函数将 TF 模型导出并保存到指定位置 (tensorflow_model_file_path
)。
需要记住的事情
a. 从 TF 到 ONNX 的转换和从 ONNX 到 TF 的转换分别通过 onnx-tensorflow
和 tf2onnx
完成。
b. onnx-tensorflow
和 tf2onnx
都支持命令行界面和提供 Python API。
接下来,我们将描述在 PyTorch 中如何执行从 ONNX 到 ONNX 的转换。
PyTorch 和 ONNX 之间的转换
在本节中,我们将解释如何将 PyTorch 模型转换为 ONNX 模型,然后再转换回来。在前一节已经覆盖了 TF 和 ONNX 之间的转换,所以通过本节结束时,你应该能够完成 TF 和 PyTorch 之间模型的转换。
将 PyTorch 模型转换为 ONNX 模型
有趣的是,PyTorch 内置支持将其模型导出为 ONNX 模型 (pytorch.org/tutorials/advanced/super_resolution_with_onnxruntime.html
)。给定一个模型,你只需要使用 torch.onnx.export
函数,如下面的代码片段所示:
import torch
pytorch_model = ...
# Input to the model
dummy_input = torch.randn(..., requires_grad=True)
onnx_model_path = "model.onnx"
# Export the model
torch.onnx.export(
pytorch_model, # model being run
dummy_input, # model input (or a tuple for multiple inputs)
onnx_model_path # where to save the model (can be a file or file-like object) )
torch.onnx.export
的第一个参数是你想要转换的 PyTorch 模型。第二个参数必须是一个表示虚拟输入的张量。换句话说,这个张量必须是模型期望输入的大小。最后一个参数是 ONNX 模型的本地路径。
触发 torch.onnx.export
函数后,你应该能够看到一个 .onnx
文件生成在你提供的路径下 (onnx_model_path
)。
现在,让我们看看如何将一个 ONNX 模型加载为 PyTorch 模型。
将 ONNX 模型转换为 PyTorch 模型。
不幸的是,PyTorch 没有内置支持加载 ONNX 模型的功能。但是,有一个名为 onnx2pytorch
的流行库可用于此转换 (github.com/ToriML/onnx2pytorch
)。假设这个库通过 pip
命令安装,下面的代码片段展示了这个转换过程:
import onnx
from onnx2pytorch import ConvertModel
onnx_model = onnx.load("model.onnx")
pytorch_model = ConvertModel(onnx_model)
我们从 onnx2pytorch
模块中需要的关键类是 ConverModel
。如前面的代码片段所示,我们将一个 ONNX 模型传递给这个类来生成一个 PyTorch 模型。
需记住的事项
a. PyTorch 内置支持将 PyTorch 模型导出为 ONNX 模型。这个过程涉及到 torch.onnx.export
函数。
b. 将 ONNX 模型导入 PyTorch 环境需要使用 onnx2pytorch
库。
在本节中,我们描述了在 ONNX 和 PyTorch 之间的转换过程。由于我们已经知道如何在 ONNX 和 TF 之间转换模型,所以 TF 和 PyTorch 之间的转换自然而然地进行了。
总结
在本章中,我们介绍了 ONNX,这是一个通用的 ML 模型表示方式。ONNX 的好处主要来自于其模型部署能力,因为它可以通过 ORT 在幕后处理环境特定的优化和转换。ONNX 的另一个优势来自于其互操作性;它可以用来将一个使用某一框架生成的 DL 模型转换为其他框架的模型。在本章中,我们特别介绍了 TensorFlow 和 PyTorch 的转换,因为它们是两个最常见的 DL 框架。
在迈向高效的深度学习模型部署的又一步中,在下一章中,我们将学习如何使用弹性 Kubernetes 服务(EKS)和 SageMaker 来建立一个模型推理端点。
第九章:扩展深度学习管道
亚马逊网络服务(AWS)在深度学习(DL)模型部署方面提供了许多可能性。在本章中,我们将介绍两种最受欢迎的服务,专为将 DL 模型部署为推理端点而设计:弹性 Kubernetes 服务(EKS)和SageMaker。
在前半部分,我们将描述基于 EKS 的方法。首先,我们将讨论如何为TensorFlow(TF)和 PyTorch 模型创建推理端点,并使用 EKS 部署它们。我们还将介绍弹性推理(EI)加速器,它可以提高吞吐量同时降低成本。EKS 集群有托管推理端点的 pod 作为 Web 服务器。作为基于 EKS 的部署的最后一个主题,我们将介绍如何根据动态流量扩展这些 pod。
在后半部分,我们将介绍基于 SageMaker 的部署。我们将讨论如何为 TF、PyTorch 和 ONNX 模型创建推理端点。此外,端点将使用亚马逊 SageMaker Neo和 EI 加速器进行优化。然后,我们将设置在 SageMaker 上运行的推理端点的自动扩展。最后,我们将通过描述如何在单个 SageMaker 推理端点中托管多个模型来结束本章。
本章节中,我们将涵盖以下主要主题:
-
使用弹性 Kubernetes 服务进行推理
-
使用 SageMaker 进行推理
技术要求
您可以从本书的 GitHub 存储库下载本章的补充材料,网址为github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_9
。
使用弹性 Kubernetes 服务进行推理
EKS 旨在通过简化复杂的集群管理过程提供用于应用部署的 Kubernetes 集群(aws.amazon.com/eks
)。有关创建 EKS 集群的详细步骤,请参阅docs.aws.amazon.com/eks/latest/userguide/create-cluster.html
。通常情况下,EKS 集群用于部署任何 Web 服务应用程序,并根据需要进行扩展。EKS 上的推理端点只是处理模型推理请求的 Web 服务应用程序。在本节中,您将学习如何在 EKS 上托管 DL 模型推理端点。
Kubernetes 集群有一个控制平面和一组节点。控制平面根据流入流量的大小进行调度和扩展决策。在调度方面,控制平面管理在给定时间点运行作业的节点。在扩展方面,控制平面根据流入端点的流量大小增加或减少 pod 的大小。EKS 在幕后管理这些组件,以便您可以专注于有效和高效地托管您的服务。
本节首先描述了如何设置 EKS 集群。然后,我们将描述如何使用 TF 和 PyTorch 创建端点,以处理 EKS 集群上的模型推断请求。接下来,我们将讨论 EI 加速器,该加速器提高了推断性能,并降低了成本。最后,我们将介绍一种根据入站流量量动态扩展服务的方法。
准备 EKS 集群
基于 EKS 的模型部署的第一步是创建适当硬件资源的 pod。在本节中,我们将使用 AWS 建议的 GPU Docker 镜像(github.com/aws/deep-learning-containers/blob/master/available_images.md
)。这些标准镜像已在 Elastic Container Registry(ECR)注册并可用,ECR 提供了一个安全、可扩展和可靠的 Docker 镜像注册表(aws.amazon.com/ecr
)。接下来,我们应该将 NVIDIA 设备插件应用到容器中。此插件使机器学习(ML)操作能够利用底层硬件以实现较低的延迟。关于 NVIDIA 设备插件的更多详细信息,建议阅读 github.com/awslabs/aws-virtual-gpu-device-plugin
。
在下面的代码片段中,我们将使用 kubectl
,kubectl
需要提供一个关于集群、用户、命名空间和认证机制信息的 YAML 文件(kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig
)。最常见的操作是 kubectl apply
,它在 EKS 集群中创建或修改资源:
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v1.12/nvidia-device-plugin.yml
在上述用例中,kubectl apply
命令根据 YAML 文件中的规范向 Kubernetes 集群应用 NVIDIA 设备插件。
配置 EKS
YAML 文件用于配置构成 Kubernetes 集群的机器以及集群内运行的应用程序。根据类型,YAML 文件中的配置可以分为两部分:deployment 和 service。deployment 部分控制 pod 内运行的应用程序。在本节中,它将用于创建 DL 模型的端点。在 EKS 环境中,运行在一个或多个 pod 上的一组应用程序称为 service。service 部分在集群上创建和配置服务。在整个 service 部分中,我们将为外部连接创建一个唯一的服务 URL,并配置入站流量的负载均衡。
在管理 EKS 集群时,命名空间可以很有用,因为它们在集群内部隔离了一组资源。要创建命名空间,可以简单地使用 kubectl create namespace
终端命令,如下所示:
kubectl create namespace tf-inference
在上述命令中,我们为接下来将在下一节创建的推断端点和服务构建了 tf-inference
命名空间。
使用 TensorFlow 模型在 EKS 上创建推断端点
在这一部分中,我们将描述一个设计用于托管 TF 模型推断端点的 EKS 配置文件(tf.yaml
)。端点由 TensorFlow Service 创建,这是一个用于部署 TF 模型的系统(www.tensorflow.org/tfx/guide/serving
)。由于我们的主要重点是在 EKS 配置上,我们将简单地假设一个训练有素的 TF 模型已经作为 .pb
文件存储在 S3 上。
首先,让我们看看配置的 Deployment
部分,负责处理端点的创建:
kind: Deployment
apiVersion: apps/v1
metadata:
name: tf-inference # name for the endpoint / deployment
labels:
app: demo
role: master
spec:
replicas: 1 # number of pods in the cluster
selector:
matchLabels:
app: demo
role: master
正如我们所见,配置的 Deployment
部分以 kind: Deployment
开始。在配置的第一部分中,我们提供了有关端点的一些元数据,并通过填写 spec
部分定义了系统设置。
端点的最重要配置在 template
下指定。我们将创建一个端点,可以通过 超文本传输协议(HTTP)请求以及 远程过程调用(gRPC)请求进行访问。HTTP 是用于网络客户端和服务器的最基本的传输数据协议。构建在 HTTP 之上,gRPC 是一个用于以二进制格式发送请求和接收响应的开源协议:
template:
metadata:
labels:
app: demo
role: master
spec:
containers:
- name: demo
image: 763104351884.dkr.ecr.us-east-1.amazonaws.com/tensorflow-inference:2.1.0-gpu-py36-cu100-ubuntu18.04 # ECR image for TensorFlow inference
command:
- /usr/bin/tensorflow_model_server # start inference endpoint
args: # arguments for the inference serving
- --port=9000
- --rest_api_port=8500
- --model_name=saved_model
- --model_base_path=s3://mybucket/models
ports:
- name: http
containerPort: 8500 # HTTP port
- name: gRPC
containerPort: 9000 # gRPC port
在 template
部分下,我们指定了要使用的 ECR 镜像(image: 763104351884.dkr.ecr.us-east-1.amazonaws.com/tensorflow-inference:2.1.0-gpu-py36-cu100-ubuntu18.04
)、创建 TF 推断端点的命令(command: /usr/bin/tensorflow_model_server
)、TF 服务的参数(args
)以及容器的端口配置(ports
)。
TF 服务参数包含模型名称(--model_name=saved_model
)、模型在 S3 上的位置(--model_base_path=s3://mybucket/models
)、用于 HTTP 访问的端口(--rest_api_port=8500
)以及用于 gRPC 访问的端口(--port=9000
)。ports
下的两个 ContainerPort
配置用于向外部连接暴露端点(containerPort: 8500
和 containerPort: 9000
)。
接下来,让我们看一下 YAML 文件的第二部分 – Service
的配置:
kind: Service
apiVersion: v1
metadata:
name: tf-inference # name for the service
labels:
app: demo
spec:
Ports:
- name: http-tf-serving
port: 8500 # HTTP port for the webserver inside the pods
targetPort: 8500 # HTTP port for access outside the pods
- name: grpc-tf-serving
port: 9000 # gRPC port for the webserver inside the pods
targetPort: 9000 # gRPC port for access outside the pods
selector:
app: demo
role: master
type: ClusterIP
配置的 Service
部分以 kind: Service
开始。在 name: http-tf-serving
部分下,我们有 port: 8500
,指的是用于 HTTP 请求的 TF 服务 Web 服务器在 Pod 内监听的端口。targetPort
指定了用于暴露相应端口的 Pod 使用的端口。我们在 name: grpc-tf-serving
部分下有另一组 gRPC 的端口配置。
要将配置应用于底层集群,您只需将此 YAML 文件提供给 kubectl apply
命令。
接下来,我们将在 EKS 上为 PyTorch 模型创建一个端点。
在 EKS 上使用 PyTorch 模型创建推断端点
在本节中,您将学习如何在 EKS 上创建 PyTorch 模型推断端点。首先,我们想介绍TorchServe,这是一个面向 PyTorch 的开源模型服务框架(pytorch.org/serve
)。它旨在简化大规模部署 PyTorch 模型的过程。用于 PyTorch 模型部署的 EKS 配置与前一节描述的 TF 模型部署非常相似。
首先,需要将 PyTorch 模型的.pth
文件转换为.mar
文件,这是 TorchServe 所需的格式(github.com/pytorch/serve/blob/master/model-archiver/README.md
)。可以使用torch-model-archiver
包实现此转换。TorchServe 和torch-model-archiver
可以通过以下方式使用pip
下载和安装:
pip install torchserve torch-model-archiver
当使用torch-model-archiver
命令进行转换时,代码如下所示:
torch-model-archiver --model-name archived_model --version 1.0 --serialized-file model.pth --handler run_inference
在前面的代码中,torch-model-archiver
命令接受model-name
(输出.mar
文件的名称,即archived_model
)、version
(PyTorch 版本 1.0)、serialized-file
(输入的 PyTorch .pth
文件,即model.pth
)和handler
(定义 TorchServe 推断逻辑的文件名;即run_inference
,指的是名为run_inference.py
的文件)。该命令将生成一个archived_model.mar
文件,该文件将通过 EKS 上传到 S3 存储桶以供端点托管。
在讨论 EKS 配置之前,我们还想介绍另一个命令,即mxnet-model-server
。该命令在 DLAMI 实例中可用,允许您托管一个运行 PyTorch 推断的 Web 服务器以处理传入请求:
mxnet-model-server --start --mms-config /home/model-server/config.properties --models archived_model=https://dlc-samples.s3.amazonaws.com/pytorch/multi-model-server/archived_model.mar
在上面的示例中,mxnet-model-server
命令使用start
参数创建了通过models
参数提供的模型的端点。正如您所看到的,models
参数指向了位于 S3 上模型的位置(archived_model=https://dlc-samples.s3.amazonaws.com/pytorch/multi-model-server/archived_model.mar
)。模型的输入参数在传递给命令的mms-config
参数的/home/model-server/config.properties
文件中指定。
现在,我们将讨论必须填写 EKS 配置中的Deployment
部分。每个组件可以保持与 TF 模型版本类似。主要区别在于template
部分,如下面的代码片段所示:
containers:
- name: pytorch-service
image: "763104351884.dkr.ecr.us-east-1.amazonaws.com/pytorch-inference:1.3.1-gpu-py36-cu101-ubuntu16.04"
args:
- mxnet-model-server
- --start
- --mms-config /home/model-server/config.properties
- --models archived_model=https://dlc-samples.s3.amazonaws.com/pytorch/multi-model-server/archived_model.mar
ports:
- name: mms
containerPort: 8080
在上述代码中,我们使用了一个不同的 Docker 镜像,其中已安装了 PyTorch(image: "763104351884.dkr.ecr.us-east-1.amazonaws.com/pytorch-inference:1.3.1-gpu-py36-cu101-ubuntu16.04"
)。配置使用mxnet-model-server
命令创建推理端点。我们将在此端点上使用的端口是8080
。我们对Service
部分所做的唯一更改可以在Ports
部分找到;我们必须确保分配了外部端口并连接到端口8080
,即端点托管的端口。同样,您可以使用kubectl apply
命令来应用更改。
在接下来的部分,我们将描述如何与由 EKS 集群托管的端点进行交互。
与 EKS 上端点的通信
现在我们有了一个运行中的端点,我们将解释如何发送请求并检索推理结果。首先,我们需要使用kubectl get services
命令来识别服务的 IP 地址,如下面的代码片段所示:
kubectl get services --all-namespaces -o wide
上述命令将返回服务及其外部 IP 地址的列表:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
tf-inference ClusterIP 10.3.xxx.xxx 104.198.xxx.xx 8500/TCP 54s
在此示例中,我们将使用在在 EKS 上使用 TensorFlow 模型创建推理端点部分中创建的tf-inference
服务。从kubectl get services
的示例输出中,我们可以看到该服务正在运行,并且具有104.198.xxx.xx
的外部 IP 地址。要通过 HTTP 访问该服务,您需要将 HTTP 的端口附加到 IP 地址:http://104.198.xxx.xx:8500
。如果您有兴趣为 IP 地址创建显式 URL,请访问aws.amazon.com/premiumsupport/knowledge-center/eks-kubernetes-services-cluster
。
要向端点发送预测请求并接收推理结果,您需要进行 POST 类型的 HTTP 请求。如果您想从终端发送请求,您可以使用以下curl
命令:
curl -d demo_input.json -X POST http://104.198.xxx.xx:8500/v1/models/demo:predict
在前述命令中,我们将 JSON 数据(demo_input.json
)发送到端点(http://104.198.xxx.xx:8500/v1/models/demo:predict
)。输入 JSON 文件demo_input.json
包含以下代码片段:
{
"instances": [1.0, 2.0, 5.0]
}
我们将从端点收到的响应数据也由以下的 JSON 数据组成:
{
"predictions": [2.5, 3.0, 4.5]
}
您可以在官方文档中找到关于输入和输出 JSON 数据结构的详细解释:www.tensorflow.org/tfx/serving/api_rest
。
如果您有兴趣使用 gRPC 而不是 HTTP,您可以在aws.amazon.com/blogs/opensource/the-versatility-of-grpc-an-open-source-high-performance-rpc-framework
找到详细信息。
恭喜!您已成功为您的模型创建了一个应用程序可以通过网络访问的端点。接下来,我们将介绍 Amazon EI 加速器,它可以降低推理延迟和 EKS 成本。
提高 EKS 端点性能使用 Amazon 弹性推理
在本节中,我们将描述如何使用 EI 加速器创建 EKS 集群,这是一种低成本的 GPU 加速。EI 加速器可以链接到 Amazon EC2 和 Sagemaker 实例或 eia2.*
类型的实例。eia2.* 实例的完整描述可以在 aws.amazon.com/machine-learning/elastic-inference/pricing
找到。
要充分利用 AWS 资源,您还需要使用 AWS Neuron (aws.amazon.com/machine-learning/neuron
) 编译您的模型。Neuron 模型的优势在于它们可以利用 Amazon EC2 Inf1 实例。这些类型的机器包含 AWS Inferentia,这是 AWS 专为云中的 ML 设计的定制芯片 (aws.amazon.com/machine-learning/inferentia
)。
AWS Neuron SDK 预安装在 AWS DL 容器和 Amazon Machine Images (AMI) 中。在本节中,我们将重点放在 TF 模型上。然而,PyTorch 模型的编译过程与此相同。有关 TF 的详细步骤可以在 docs.aws.amazon.com/dlami/latest/devguide/tutorial-inferentia-tf-neuron.html
找到,PyTorch 的步骤可以在 docs.aws.amazon.com/dlami/latest/devguide/tutorial-inferentia-pytorch-neuron.html
找到。
将 TF 模型编译为 Neuron 模型可以通过使用 TF 的 tf.neuron.saved_model.compile
函数来实现。
import tensorflow as tf
tf.neuron.saved_model.compile(
tf_model_dir, # input TF model dir
neuron_model_dir # output neuron compiled model dir
)
对于此功能,我们只需提供输入模型的位置 (tf_model_dir
) 和我们想要存储输出 Neuron 模型的位置 (neuron_model_dir
)。正如我们将 TF 模型上传到 S3 存储桶以进行端点创建一样,我们还需要将 Neuron 模型移动到 S3 存储桶。
再次提到,您需要对 EKS 配置进行的更改仅需要在 Deployment
部分的 template
部分完成。以下代码片段描述了配置的更新部分:
containers:
- name: neuron-demo
image: 763104351884.dkr.ecr.us-east-1.amazonaws.com/tensorflow-inference-neuron:1.15.4-neuron-py37-ubuntu18.04
command:
- /usr/local/bin/entrypoint.sh
args:
- --port=8500
- --rest_api_port=9000
- --model_name=neuron_model
- --model_base_path=s3://mybucket/neuron_model/
ports:
- name: http
containerPort: 8500 # HTTP port
- name: gRPC
containerPort: 9000 # gRPC port
从上述配置中我们注意到的第一件事是,它与我们在 在 EKS 上使用 TensorFlow 模型创建推断端点 部分描述的非常相似。区别主要来自于 image
、command
和 args
部分。首先,我们需要使用带有 AWS Neuron 和 TensorFlow Serving 应用程序的 DL 容器(image: 763104351884.dkr.ecr.us-east-1.amazonaws.com/tensorflow-inference-neuron:1.15.4-neuron-py37-ubuntu18.04
)。接下来,通过 command
键传递模型文件的入口点脚本:/usr/local/bin/entrypoint.sh
。入口点脚本用于使用 args
启动 Web 服务器。要从 Neuron 模型创建端点,必须指定存储目标 Neuron 模型的 S3 存储桶作为 model_base_path
参数(--model_base_path=s3://mybucket/neuron_model/
)。
要将更改应用到集群,只需将更新后的 YAML 文件传递给 kubectl apply
命令。
最后,我们将看一下 EKS 的自动缩放功能,以提高端点的稳定性。
动态调整 EKS 集群大小使用自动缩放
EKS 集群可以根据流量量自动调整集群大小。水平 Pod 自动缩放的理念是根据传入请求的增加来增加运行应用程序的 Pod 数量。类似地,当传入流量减少时,一些 Pod 将被释放。
通过 kubectl apply
命令部署应用程序后,可以使用 kubectl autoscale
命令设置自动缩放,如下所示:
kubectl autoscale deployment <application-name> --cpu-percent=60 --min=1 --max=10
如前例所示,kubectl autoscale
命令接受 YAML 文件的 Deployment
部分中指定的应用程序名称,cpu-percent
(用于调整集群大小的 CPU 百分比阈值),min
(要保留的最小 Pod 数),以及 max
(要启动的最大 Pod 数)。总结一下,示例命令将根据流量量在 1 到 10 个 Pod 中运行服务,保持 CPU 使用率在 60%。
要记住的事情
a. EKS 旨在通过简化动态流量的复杂集群管理,为应用程序部署提供 Kubernetes 集群。
b. 使用 YAML 文件配置组成 Kubernetes 集群的机器和集群内运行的应用程序。配置的两个部分,Deployment
和 Service
,分别控制 Pod 内运行的应用程序,并为底层目标集群配置服务。
c. 可以使用 TF 和 PyTorch 模型在 EKS 集群上创建和托管推断端点。
d. 利用使用 AWS Neuron 编译的模型来利用 EI 加速器,可以提高推断延迟,同时节省 EKS 集群的运营成本。
b. 可以配置 EKS 集群根据流量量动态调整大小。
在这一部分中,我们讨论了基于 EKS 的 TF 和 PyTorch 模型部署。我们描述了如何使用 AWS Neuron 模型和 EI 加速器来提高服务性能。最后,我们介绍了自动扩展以更有效地利用可用资源。在下一部分中,我们将看看另一个 AWS 服务用于托管推理端点:SageMaker。
使用 SageMaker 进行推断
在本节中,您将学习如何使用 SageMaker 而不是 EKS 集群创建端点。首先,我们将描述创建推理端点的与框架无关的方法(即Model
类)。然后,我们将查看使用TensorFlowModel
和 TF 特定的Estimator
类创建 TF 端点的方法。接下来的部分将重点介绍使用PyTorchModel
类和 PyTorch 特定的Estimator
类创建 PyTorch 模型的端点。此外,我们还将介绍如何从 ONNX 模型构建端点。到此为止,我们应该有一个正在为传入请求运行模型预测的服务。之后,我们将描述如何使用AWS SageMaker Neo和 EI 加速器来提高服务质量。最后,我们将介绍自动扩展并描述如何在单个端点上托管多个模型。
如在《第五章》的在云中利用 SageMaker 进行 ETL部分中所述,SageMaker 提供了一个名为 SageMaker Studio 的内置笔记本环境。我们在本节中包含的代码片段是为在这个笔记本中执行而准备的。
使用 Model 类设置推理端点
一般来说,SageMaker 提供三种不同的类来创建端点。最基本的是Model
类,支持各种 DL 框架的模型。另一个选项是使用特定于框架的Model
类。最后一个选项是使用Estimator
类。在本节中,我们将看看第一种选项,即Model
类。
在深入端点创建过程之前,我们需要确保已经适当地准备了必要的组件;为 SageMaker 配置了正确的 IAM 角色,并且训练好的模型应该在 S3 上可用。IAM 角色可以在笔记本中如下准备:
from sagemaker import get_execution_role
from sagemaker import Session
# IAM role of the notebook
role = get_execution_role()
# A Session object for SageMaker
sess = Session()
# default bucket object
bucket = sess.default_bucket()
在上述代码中,已设置了 IAM 访问角色和默认存储桶。要加载 SageMaker 笔记本的当前 IAM 角色,您可以使用sagemaker.get_execution_role
函数。要创建一个 SageMaker 会话,您需要为Session
类创建一个实例。Session
实例的default_bucket
方法将创建一个以sagemaker-{region}-{aws-account-id}
格式命名的默认存储桶。
在将模型上传到 S3 存储桶之前,模型需要被压缩为.tar
文件。以下代码片段描述了如何压缩模型并将压缩后的模型上传到笔记本中的目标存储桶:
import tarfile
model_archive = "model.tar.gz"
with tarfile.open(model_archive, mode="w:gz") as archive:
archive.add("export", recursive=True)
# model artifacts uploaded to S3 bucket
model_s3_path = sess.upload_data(path=model_archive, key_prefix="model")
在前面的代码片段中,使用tarfile
库执行压缩。Session
实例的upload_data
方法用于将编译后的模型上传到与 SageMaker 会话链接的 S3 存储桶。
现在,我们准备创建一个Model
类的实例。在这个特定的示例中,我们将假设该模型已经使用 TF 训练完成:
from sagemaker.tensorflow.serving import Model
# TF version
tf_framework_version = "2.8"
# Model instance for inference endpoint creation
sm_model = Model(
model_data=model_s3_path, # S3 path for model
framework_version=tf_framework_version, # TF version
role=role) # IAM role of the notebook
predictor = sm_model.deploy(
initial_instance_count=1, # number of instances used
instance_type="ml.c5.xlarge")
如前面的代码所示,Model
类的构造函数接受model_data
(压缩模型文件位于的 S3 路径)、framework_version
(TF 的版本)和role
(笔记本的 IAM 角色)。Model
实例的deploy
方法处理实际的端点创建。它接受initial_instance_count
(启动端点的实例数)和instance_type
(要使用的 EC2 实例类型)。
另外,您可以提供一个指定的image
和删除framework_version
。在这种情况下,端点将使用为image
参数指定的 Docker 镜像创建。它应指向 ECR 上的一个镜像。
接下来,我们将讨论如何使用创建的端点从笔记本触发模型推理。deploy
方法将返回一个Predictor
实例。如下代码片段所示,您可以通过Predictor
实例的predict
函数实现这一点。您只需传递表示输入的一些 JSON 数据:
input = {
"instances": [1.0, 2.0, 5.0]
}
results = predictor.predict(input)
predict
函数的输出results
是 JSON 数据,我们的示例中如下所示:
{
"predictions": [2.5, 3.0, 4.5]
}
predict
函数支持不同格式的数据,例如 JSON、CSV 和多维数组。如果您需要使用除 JSON 以外的类型,请参考sagemaker.readthedocs.io/en/stable/frameworks/tensorflow/using_tf.html#tensorflow-serving-input-and-output
。
触发模型推理的另一种选项是使用boto3
库中的SageMaker.Client
类。SageMaker.Client
类是代表 Amazon SageMaker 服务的低级客户端。在以下代码片段中,我们正在创建一个SageMaker.Client
实例,并演示如何使用invoke_endpoint
方法访问端点:
import boto3
client = boto3.client("runtime.sagemaker")
# SageMaker Inference endpoint name
endpoint_name = "run_model_prediction"
# Payload for inference which consists of the input data
payload = "..."
# SageMaker endpoint called to get HTTP response (inference)
response = client.invoke_endpoint(
EndpointName=endpoint_name,
ContentType="text/csv", # content type
Body=payload # input data to the endpoint)
如前面的代码片段所示,invoke_endpoint
方法接受EndpointName
(端点名称;即run_model_prediction
)、ContentType
(输入数据类型;即"text/csv"
)和Body
(用于模型预测的输入数据;即payload
)。
实际上,许多公司利用 Amazon API Gateway (aws.amazon.com/api-gateway
) 和 AWS Lambda (aws.amazon.com/lambda
) 与 SageMaker 端点一起在无服务器架构中通信,以与部署的模型进行交互。有关详细设置,请参阅 aws.amazon.com/blogs/machine-learning/call-an-amazon-sagemaker-model-endpoint-using-amazon-api-gateway-and-aws-lambda
。
接下来,我们将解释创建端点的框架特定方法。
使用 TensorFlow 设置推断端点
在本节中,我们将描述专为 TF 设计的 Model
类 - TensorFlowModel
类。然后,我们将解释如何使用 TF 特定的 Estimator
类创建端点。本节代码片段的完整版本可以在 github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_9/sagemaker
找到。
使用 TensorFlowModel
类设置 TensorFlow 推断端点
TensorFlowModel
类是为 TF 模型设计的 Model
类。如下代码片段所示,该类可以从 sagemaker.tensorflow
模块导入,并且其使用方式与 Model
类相同:
from sagemaker.tensorflow import TensorFlowModel
# Model instance
sm_model = TensorFlowModel(
model_data=model_s3_path,
framework_version=tf_framework_version,
role=role) # IAM role of the notebook
# Predictor
predictor = sm_model.deploy(
initial_instance_count=1,
instance_type="ml.c5.xlarge")
TensorFlowModel
类的构造函数接受与 Model
类相同的参数:上传模型的 S3 路径 (model_s3_path
)、TF 框架版本 (Tf_framework_version
) 和 SageMaker 的 IAM 角色 (role
)。此外,您可以通过提供 entry_point
来提供用于模型推断输入和输出的预处理和后处理的 Python 脚本。在这种情况下,脚本需要命名为 inference.py
。更多详情,请参阅 sagemaker.readthedocs.io/en/stable/frameworks/tensorflow/deploying_tensorflow_serving.html#providing-python-scripts-for-pre-post-processing
。
TensorFlowModel
类作为 Model
的子类,通过 deploy
方法也提供了一个 Predictor
实例。其使用方式与我们在前一节中描述的相同。
接下来,您将学习如何使用 Estimator
类部署您的模型,我们已经在第六章,高效模型训练 中介绍了这一类用于 SageMaker 模型训练。
使用 Estimator 类设置 TensorFlow 推断端点
如在Chapter 6,Efficient Model Training的Training a TensorFlow model using SageMaker部分介绍的那样,SageMaker 提供了支持在 SageMaker 上进行模型训练的Estimator
类。同一类也可用于创建和部署推断端点。在下面的代码片段中,我们使用了为 TF 设计的Estimator
类,即sagemaker.tensorflow.estimator.TensorFlow
,来训练一个 TF 模型,并使用训练后的模型部署一个端点:
from sagemaker.tensorflow.estimator import TensorFlow
# create an estimator
estimator = TensorFlow(
entry_point="tf-train.py",
...,
instance_count=1,
instance_type="ml.c4.xlarge",
framework_version="2.2",
py_version="py37" )
# train the model
estimator.fit(inputs)
# deploy the model and returns predictor instance for inference
predictor = estimator.deploy(
initial_instance_count=1,
instance_type="ml.c5.xlarge")
如前面的代码片段所示,sagemaker.tensorflow.estimator.TensorFlow
类接受以下参数:entry_point
(处理训练的脚本,即"tf-train.py"
)、instance_count
(使用的实例数,即1
)、instance_type
(实例的类型,即"ml.c4.xlarge"
)、framework_version
(PyTorch 的版本,即"2.2"
)和py_version
(Python 的版本,即"py37"
)。Estimator
实例的fit
方法执行模型训练。用于创建和部署端点的关键方法是deploy
方法,它基于提供的条件创建和托管一个端点:initial_instance_count
(1
)实例,instance_type
("ml.c5.xlarge"
)。Estimator
类的deploy
方法返回一个Predictor
实例,就像Model
类的情况一样。
本节中,我们解释了如何在 SageMaker 上为 TF 模型创建端点。在下一节中,我们将探讨 SageMaker 如何支持 PyTorch 模型。
设置一个 PyTorch 推断端点
本节旨在介绍在 SageMaker 上创建和托管 PyTorch 模型端点的不同方法。首先,我们将介绍为 PyTorch 模型设计的Model
类:PyTorchModel
类。接着,我们将描述 PyTorch 模型的Estimator
类。本节中代码片段的完整实现可以在github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/blob/main/Chapter_9/sagemaker/pytorch-inference.ipynb
找到。
使用PyTorchModel
类设置 PyTorch 推断端点
类似于TensorFlowModel
类,专门为 PyTorch 模型设计了一个Model
类,即PyTorchModel
。可以按以下方式实例化它:
from sagemaker.pytorch import PyTorchModel
model = PyTorchModel(
entry_point="inference.py",
source_dir="s3://bucket/model",
role=role, # IAM role for SageMaker
model_data=pt_model_data, # model file
framework_version="1.11.0", # PyTorch version
py_version="py3", # python version
)
如前面的代码片段所示,构造函数接受entry_point
(定义数据的自定义前后处理逻辑)、source_dir
(入口点脚本的 S3 路径)、role
(SageMaker 的 IAM 角色)、model_data
(模型的 S3 路径)、framework_version
(PyTorch 的版本)和py_version
(Python 的版本)作为参数。
由于PyTorchModel
类继承自Model
类,它提供了deploy
函数,用于创建和部署端点,如使用 Model 类设置 PyTorch 推断端点章节中所述。
接下来,我们将介绍专为 PyTorch 模型设计的Estimator
类。
使用 Estimator 类设置 PyTorch 推断端点
如果没有训练好的 PyTorch 模型可用,可以使用sagemaker.pytorch.estimator.PyTorch
类来训练和部署模型。训练可以通过fit
方法来完成,如Chapter 6章节使用 SageMaker 训练 PyTorch 模型中所述,高效的模型训练。作为Estimator
类,sagemaker.pytorch.estimator.PyTorch
类提供了与TensorFlow
中的Estimator
类相同的功能,我们在使用 Estimator 类设置 TensorFlow 推断端点章节中有所涉及。在下面的代码片段中,我们正在为 PyTorch 模型创建一个Estimator
实例,训练模型,并创建一个端点:
from sagemaker.pytorch.estimator import PyTorch
# create an estimator
estimator = PyTorch(
entry_point="pytorch-train.py",
...,
instance_count=1,
instance_type="ml.c4.xlarge",
framework_version="1.11",
py_version="py37")
# train the model
estimator.fit(inputs)
# deploy the model and returns predictor instance for inference
predictor = estimator.deploy(
initial_instance_count=1,
instance_type="ml.c5.xlarge")
如前面的代码片段所示,sagemaker.pytorch.estimator.PyTorch
的构造函数接受与为 TF 设计的Estimator
类相同的参数集:entry_point
(处理训练的脚本;即"pytorch-train.py"
)、instance_count
(要使用的实例数量;即1
)、instance_type
(EC2 实例的类型;即"ml.c4.xlarge"
)、framework_version
(PyTorch 版本;即"1.11.0"
)和py_version
(Python 版本;即"py37"
)。模型训练(fit
方法)和部署(deploy
方法)的方式与使用 Estimator 类设置 TensorFlow 推断端点章节中的前例相同。
在本节中,我们介绍了如何以两种不同的方式部署 PyTorch 模型:使用PyTorchModel
类和Estimator
类。接下来,我们将学习如何在 SageMaker 上为 ONNX 模型创建端点。
建立一个来自 ONNX 模型的推断端点
如前一章节所述,Chapter 8章节简化深度学习模型部署中,DL 模型通常会转换为开放神经网络交换(ONNX)模型进行部署。在本节中,我们将描述如何在 SageMaker 上部署 ONNX 模型。
最标准的方法是使用基础的Model
类。如使用 Model 类设置 TensorFlow 推断端点章节中所述,Model
类支持各种类型的 DL 模型。幸运的是,它也为 ONNX 模型提供了内置支持:
from sagemaker.model import Model
# Load an ONNX model file for endpoint creation
sm_model= Model(
model_data=model_data, # path for an ONNX .tar.gz file
entry_point="inference.py", # an inference script
role=role,
py_version="py3",
framework="onnx",
framework_version="1.4.1", # ONNX version
)
# deploy model
predictor = sm_model.deploy(
initial_instance_count=1, # number of instances to use
instance_type=ml.c5.xlarge) # instance type for deploy
在上述示例中,我们在 S3 上有一个训练好的 ONNX 模型。在 Model
实例创建中,关键部分来自于 framework="onnx"
。我们还需要为 framework_version
提供 ONNX 框架版本。在本例中,我们使用的是 ONNX 框架版本 1.4.0。其他所有内容几乎与之前的示例完全相同。再次强调,deploy
函数用于创建和部署端点;将返回一个 Predictor
实例用于模型预测。
常见做法还包括使用 TensorFlowModel
和 PyTorchModel
类来从 ONNX 模型创建端点。以下代码片段展示了这些用例:
from sagemaker.tensorflow import TensorFlowModel
# Load ONNX model file as a TensorFlowModel
tf_model = TensorFlowModel(
model_data=model_data, # path to the ONNX .tar.gz file
entry_point="tf_inference.py",
role=role,
py_version="py3", # Python version
framework_version="2.1.1", # TensorFlow version
)
from sagemaker.pytorch import PyTorchModel
# Load ONNX model file as a PyTorchModel
pytorch_model = PyTorchModel(
model_data=model_data, # path to the ONNX .tar.gz file
entry_point="pytorch_inference.py",
role=role,
py_version="py3", # Python version
framework_version="1.11.0", # PyTorch version
)
上述代码片段不言自明。这两个类都接受一个 ONNX 模型路径 (model_data
),一个推断脚本 (entry_point
),一个 IAM 角色 (role
),一个 Python 版本 (py_version
),以及每个框架的版本 (framework_version
)。与 Model
类如何部署端点一样,deploy
方法将从每个模型创建并托管一个端点。
尽管端点允许我们随时获取动态输入数据的模型预测结果,但有些情况下,您需要对存储在 S3 存储桶中的整个输入数据进行推断,而不是逐个馈送它们。因此,我们将看看如何利用 Batch Transform 来满足这一需求。
使用 Batch Transform 处理批量预测请求
我们可以利用 SageMaker 的 Batch Transform 功能(docs.aws.amazon.com/sagemaker/latest/dg/batch-transform.html
)在一个队列中对大型数据集进行推断。使用 sagemaker.transformer.Transformer
类,您可以在 S3 上对任何数据集进行批量模型预测,而无需持久化端点。具体细节包含在以下代码片段中:
from sagemaker import transformer
bucket_name = "my-bucket" # S3 bucket with data
# location of the input data
input_location = "s3://{}/{}".format(bucket_name, "input_data")
# location where the predictions will be stored
batch_output = "s3://{}/{}".format(bucket_name, "batch-results")
# initialize the transformer object
transformer = transformer.Transformer(
base_transform_job_name="Batch-Transform", # job name
model_name=model_name, # Name of the inference endpoint
max_payload= 5, # maximum payload
instance_count=1, # instance count to start with
instance_type="ml.c4.xlarge", # ec2 instance type
output_path=batch_output # S3 for batch inference output)
# triggers the prediction on the whole dataset
tf_transformer = transformer.transformer(
input_location, # input S3 path for input data
content_type="text/csv", # input content type as CSV
split_type="Line" # split type for input as Line)
如上述代码所示,sagemaker.transformer.Transformer
类接受 base_transformer_job_name
(转换作业的作业名称)、model_name
(保存推断流水线的模型名称)、max_payload
(允许的最大有效载荷,以 MB 为单位)、instance_count
(要启动的 EC2 实例数)、instance_type
(EC2 实例的类型)和 output_path
(存储输出的 S3 路径)。transformer
方法将触发指定数据集上的模型预测。它接受以下参数:input_location
(输入数据所在的 S3 路径)、content_type
(输入数据的内容类型;即 "text/csv"
)、以及 split_type
(控制如何分割输入数据;使用 "Line"
将数据的每一行作为模型的单独输入)。实际上,许多公司还利用 SageMaker 处理作业(docs.aws.amazon.com/sagemaker/latest/APIReference/API_ProcessingJob.html
)执行批量推断,但我们不会详细讨论此事。
到目前为止,我们已经了解了 SageMaker 如何支持托管推断终端以处理实时预测请求,并对在 S3 上静态数据集进行模型预测。接下来,我们将描述如何使用 AWS SageMaker Neo 进一步提高部署模型的推断延迟。
提升 SageMaker 终端性能,利用 AWS SageMaker Neo
在本节中,我们将解释 SageMaker 如何通过利用底层硬件资源(EC2 实例或移动设备)进一步提升应用程序的性能。其思想是使用 AWS SageMaker Neo 编译经过训练的 DL 模型(aws.amazon.com/sagemaker/neo
)。编译后,生成的 Neo 模型可以更好地利用底层设备,从而降低推断延迟。AWS SageMaker Neo 支持不同框架的模型(TF、PyTorch、MxNet 和 ONNX),以及各种类型的硬件(操作系统、芯片、架构和加速器)。支持的完整资源列表详见 docs.aws.amazon.com/sagemaker/latest/dg/neo-supported-devices-edge-devices.html
。
可通过 Model
类的 compile
方法生成 Neo 模型。compile
方法返回一个支持终端创建的 Estimator
实例。让我们看下面的示例以获取详细信息:
# sm_model created from Model
sm_model = Model(...)
# instance type of which the model will be optimized for
instance_family = "ml_c5"
# DL framework
framework = "tensorflow"
compilation_job_name = "tf-compile"
compiled_model_path = "s3:..."
# shape of an input data
data_shape = {"inputs":[1, data.shape[0], data.shape[1]]}
estimator = sm_model.compile(
target_instance_family=instance_family,
input_shape=data_shape,
ob_name=compilation_job_name,
role=role,
framework=framework,
framework_version=tf_framework_version,
output_path=compiled_model_path)
# deploy the neo model on instances of the target type
predictor = estimator.deploy(
initial_instance_count=1,
instance_type=instance_family)
在前述代码中,我们从名为 sm_model
的 Model
实例开始。我们调用 compile
方法将加载的模型编译为 Neo 模型。以下是参数列表的描述:
-
target_instance_family
:优化模型的 EC2 实例类型 -
input_shape
:输入数据的形状 -
job_name
:编译作业的名称 -
role
:编译模型输出的 IAM 角色 -
framework
:如 TF 或 PyTorch 的 DL 框架 -
framework_version
:要使用的框架版本 -
output_path
:编译模型将存储的输出 S3 路径
Estimator
实例包括一个deploy
函数,用于创建端点。输出是一个Predictor
实例,您可以使用它来运行模型预测。在上述示例中,我们优化了我们的模型,以在ml_c5
类型的实例上表现最佳。
接下来,我们将描述如何将 EI 加速器集成到运行在 SageMaker 上的端点中。
使用 Amazon Elastic Inference 改善 SageMaker 端点性能
在使用 Amazon Elastic Inference 改善 EKS 端点性能部分,我们描述了如何利用 EI 加速器来降低推断端点的运行成本,同时通过利用可用的 GPU 设备来提高推断延迟。在本节中,我们将涵盖 SageMaker 的 EI 加速器集成。
必要的更改非常简单;您只需在触发Model
实例的deploy
方法时提供accelerator_type
即可:
# deploying a Tensorflow/PyTorch/other model files using EI
predictor = sm_model.deploy(
initial_instance_count=1, # ec2 initial count
instance_type="ml.m4.xlarge", # ec2 instance type
accelerator_type="ml.eia2.medium" # accelerator type)
在上述代码中,deploy
方法为给定的Model
实例创建端点。要将 EI 加速器附加到端点上,您需要在默认参数(initial_instance_count
和instance_type
)之上指定所需的加速器类型(accelerator_type
)。有关在 SageMaker 端点中使用 EI 的完整说明,请参阅docs.aws.amazon.com/sagemaker/latest/dg/ei.html
。
在接下来的部分,我们将探讨 SageMaker 的自动扩展功能,这使我们能够更好地处理传入流量的变化。
使用自动扩展动态调整 SageMaker 端点的大小
类似于 EKS 集群支持自动扩展根据流量变化自动调整端点的大小,SageMaker 也提供了自动扩展功能。配置自动扩展涉及配置扩展策略,该策略定义了何时进行扩展以及在扩展时创建和销毁多少资源。可以从 SageMaker Web 控制台配置 SageMaker 端点的扩展策略。以下步骤描述了如何在从 SageMaker 笔记本创建的推断端点上配置自动扩展:
-
访问 SageMaker Web 控制台,
console.aws.amazon.com/sagemaker/
,并在左侧导航面板中的推断下点击端点。您可能需要提供凭据以登录。 -
接下来,您必须选择要配置的端点名称。在端点运行时设置下,选择需要配置的模型变体。此功能允许您在单个端点中部署多个模型版本,每个版本都会启动一个容器。有关此功能的详细信息,请参见
docs.aws.amazon.com/sagemaker/latest/APIReference/API_runtime_InvokeEndpoint.html
。 -
在端点运行时设置下,选择配置自动扩展。这将带您进入配置变体自动扩展页面:
图 9.1 – SageMaker Web 控制台的配置变体自动扩展页面
-
在最小实例数字段中输入要维护的最小实例数。最小值为 1。此值定义将始终保留的最小实例数量。
-
在最大实例数字段中输入要维护的扩展策略的最大实例数。此值定义了在峰值流量时允许的最大实例数量。
-
填写SageMakerVariantInvocationsPerInstance字段。每个端点可以在一个或多个 EC2 实例上托管的单个端点中部署多个模型(或模型版本)。SageMakerVariantInvocationsPerInstance定义了每个模型变体每分钟允许的最大调用次数。此值用于负载均衡。有关计算此字段正确数量的详细信息,请参见
docs.aws.amazon.com/sagemaker/latest/dg/endpoint-scaling-loadtest.html
。 -
填写缩放缓冲时间和扩展缓冲时间。这些指示 SageMaker 在检查下一轮缩放之前等待多长时间。
-
选择禁用缩放复选框。在流量增加时,作为扩展过程的一部分会启动更多实例。但是,如果在增加后立即减少流量,这些实例可能会在缩小过程中被快速删除。为避免新创建的实例在创建后立即释放,必须选择此复选框。
-
单击保存按钮以应用配置。
一旦单击保存按钮,将会将扩展应用于所选的模型变体。SageMaker 将根据传入的流量增加和减少实例数量。有关自动扩展的更多详细信息,请参阅 docs.aws.amazon.com/autoscaling/ec2/userguide/as-instance-termination.html
。
作为基于 SageMaker 的端点的最后一个主题,我们将描述如何通过单个端点部署多个模型。
在单个 SageMaker 推断端点上托管多个模型
SageMaker 支持通过 多模态端点 (MME) 部署多个模型在一个端点上。在设置 MME 之前,有几件事情需要牢记。首先,建议如果希望保持低延迟,则设置多个端点。其次,容器只能部署来自同一 DL 框架的模型。对于希望托管来自不同框架的模型的用户,建议阅读 docs.amazonaws.cn/en_us/sagemaker/latest/dg/multi-container-direct.html
。当模型大小和预期表现相似时,MME 的效果最佳。
以下步骤描述了如何设置 MME:
-
使用您的 AWS 凭证访问 SageMaker Web 控制台
console.aws.amazon.com/sagemaker
。 -
在左侧导航面板的 推理 部分下选择 模型。然后,点击右上角的 创建模型 按钮。
-
为 模型名称 字段输入一个值。这将用于在 SageMaker 上下文中唯一标识目标模型。
-
选择一个具有 AmazonSageMakerFullAccess IAM 策略的 IAM 角色。
-
在容器定义部分,选择多个模型选项,并提供推理代码镜像的位置以及模型工件的位置(参见 图 9.2):
图 9.2 – SageMaker Web 控制台的多模态端点配置页面
前一个字段用于使用自定义 Docker 镜像部署您的模型(docs.aws.amazon.com/sagemaker/latest/dg/your-algorithms-inference-code.html
)。在此字段中,您应提供镜像注册路径,其中镜像位于 Amazon ECR 内。后一个字段指定了模型工件所在的 S3 路径。
-
此外,填写 容器主机名 字段。这指定了推理代码镜像将被创建的主机的详细信息。
-
在最后选择 创建模型 按钮。
配置了 MME 后,可以使用 boto3
库中的 SageMaker.Client
来测试端点,如下面的代码片段所示:
import boto3
# Sagemaker runtime client instance
runtime_sagemaker_client = boto3.client("sagemaker-runtime")
# send a request to the endpoint targeting specific model
response = runtime_sagemaker_client.invoke_endpoint(
EndpointName="<ENDPOINT_NAME>",
ContentType="text/csv",
TargetModel="<MODEL_FILENAME>.tar.gz",
Body=body)
在上述代码中,SageMaker.Client
实例的invoke_endpoint
函数向创建的端点发送请求。invoke_endpoint
函数接受EndpointName
(创建的端点的名称)、ContentType
(请求主体中的数据类型)、TargetModel
(以.tar.gz
格式压缩的模型文件;用于指定请求将调用的目标模型)和Body
(ContentType
中的输入数据)。从调用返回的response
变量包含预测结果。有关与端点通信的完整描述,请参阅docs.aws.amazon.com/sagemaker/latest/dg/invoke-multi-model-endpoint.html
。
记住的事情
a. SageMaker 通过其内置的Model
类和Estimator
类支持端点创建。这些类支持使用各种 DL 框架(包括 TF、PyTorch 和 ONNX)训练的模型。专为 TF 和 PyTorch 框架设计的Model
类也存在:TensorFlowModel
和PyTorchModel
。
b. 使用 AWS SageMaker Neo 编译模型后,模型可以更好地利用底层硬件资源,表现出更高的推理性能。
c. SageMaker 可以配置为使用 EI 加速器,从而降低推理端点的运行成本并提高推理延迟。
d. SageMaker 包含自动扩展功能,根据传入流量的量动态调整端点的规模。
e. SageMaker 支持通过 MME 在单个端点上部署多个模型。
在本节中,我们描述了 SageMaker 为部署 DL 模型作为推理端点提供的各种功能。
摘要
在本章中,我们描述了两种最受欢迎的 AWS 服务,专为将 DL 模型部署为推理端点设计:EKS 和 SageMaker。对于这两个选项,我们从最简单的设置开始:使用 TF、PyTorch 或 ONNX 模型创建推理端点。然后,我们解释了如何使用 EI 加速器、AWS Neuron 和 AWS SageMaker Neo 来提高推理端点的性能。我们还介绍了如何设置自动扩展以更有效地处理流量变化。最后,我们讨论了 SageMaker 的 MME 功能,用于在单个推理端点上托管多个模型。
在下一章中,我们将探讨各种模型压缩技术:网络量化、权重共享、网络修剪、知识蒸馏和网络架构搜索。这些技术将进一步提高推理效率。
第十章:提高推理效率
当深度学习(DL)模型部署在边缘设备上时,推理效率通常令人不满意。这些问题主要源于训练网络的大小,因为它需要大量计算。因此,许多工程师和科学家在将 DL 模型部署到边缘设备上时通常会在速度和准确性之间进行权衡。此外,他们专注于减少模型大小,因为边缘设备通常具有有限的存储空间。
在本章中,我们将介绍一些技术,以改善推理延迟,同时尽可能保持原始性能。首先,我们将讨论网络量化,这是一种通过使用较低精度的数据格式来减小网络尺寸的技术。接下来,我们将谈论权重共享,也被称为权重聚类。这是一个非常有趣的概念,其中少量模型权重值在整个网络中共享,从而减少存储训练模型所需的磁盘空间。我们还将讨论网络修剪,它涉及消除网络内部的不必要连接。虽然这三种技术最受欢迎,但我们还将介绍另外两个有趣的主题:知识蒸馏和网络架构搜索。这两种技术通过直接在训练期间修改网络架构来实现模型大小的减小和推理延迟的改进。
在本章中,我们将介绍以下主要内容:
-
网络量化 – 减少模型参数使用的位数
-
权重共享 – 减少不同权重值的数量
-
网络修剪 – 消除网络内部的不必要连接
-
知识蒸馏 – 通过模仿预测获得更小的网络
-
网络架构搜索 – 寻找最有效的网络架构
技术要求
您可以从本书的 GitHub 存储库下载本章的补充材料,链接为github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_10
。
在深入讨论各个技术之前,我们想介绍两个构建在TensorFlow(TF)之上的库。第一个是TensorFlow Lite(TF Lite),它负责在移动设备、微控制器和其他边缘设备上部署 TF 模型(www.tensorflow.org/lite
)。我们将描述的一些技术仅适用于 TF Lite。另一个库称为 TensorFlow Model Optimization Toolkit。此库旨在为 TF 模型提供各种优化技术(www.tensorflow.org/model_optimization
)。
网络量化 – 减少模型参数使用的位数
如果我们详细看一下 DL 模型训练,您会注意到模型学习处理噪声输入。换句话说,模型试图为其训练的数据构建一般化,以便即使在传入数据中存在一些噪声时,它也能生成合理的预测。此外,DL 模型在训练后最终会使用特定范围的数值进行推断。基于这种思路,网络量化旨在为这些值使用更简单的表示。
如 图 10.1 所示,网络量化,也称为模型量化,是将模型与之交互的数值范围重新映射到可以用较少比特表示的数字系统的过程 - 例如,使用 8 位而不是 32 位来表示浮点数。这样的修改在 DL 模型部署中具有额外的优势,因为边缘设备通常不支持基于 32 位浮点数的稳定算术:
图 10.1 – 展示网络量化中从浮点 32 到整数 8 的数字系统重映射的插图
不幸的是,网络量化涉及的不仅仅是将高精度数字转换为低精度。这是因为 DL 模型推断涉及产生比输入精度更高的数字的算术。在本章中,我们将探讨网络量化中以不同方式克服挑战的各种选项。如果您对了解更多关于网络量化的信息感兴趣,我们推荐阅读 Gholami 等人的《用于高效神经网络推断的量化方法综述》。
网络量化技术可以分为两个领域。第一个是后训练量化,另一个是量化感知训练。前者旨在量化已经训练过的模型,而后者通过以较低精度训练模型来减少由量化过程引起的精度降低。
幸运的是,这两种技术在标准 DL 框架中都可用:TF 和 PyTorch。在接下来的章节中,我们将看看如何在这些框架中执行网络量化。
执行后训练量化
首先,我们将看看 TF 和 PyTorch 如何支持后训练量化。修改非常简单,只需要几行额外的代码。让我们从 TF 开始。
在 TensorFlow 中执行后训练量化
默认情况下,DL 模型在必要的计算和变量中使用 32 位浮点数。在以下示例中,我们将演示动态范围量化,其中仅将固定参数(如权重)量化为使用 16 位而不是 32 位。请注意,您需要安装 TF Lite 来进行 TF 中的后训练量化:
import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]
tflite_quant_model = converter.convert()
从量化中,我们获得了一个 TF Lite 模型。在上述代码片段中,我们使用tf.lite.TFLiteConverter.from_saved_model
函数加载训练好的 TF 模型,并获取了一个量化的 TF Lite 模型。在触发转换之前,我们需要配置一些东西。首先,我们必须设置量化模型权重的优化策略(converter.optimizations = [tf.lite.Optimize.DEFAULT]
)。然后,我们需要指定我们希望从量化中获取 16 位权重(converter.target_spec.supported_types = [tf.float16]
)。实际的量化发生在触发convert
函数时。在上述代码中,如果我们不为supported_types
指定 16 位浮点类型,我们将使用 8 位整数量化模型。
接下来,我们想介绍全整数量化,其中模型推断的每个组件(输入、激活以及权重)都被量化为较低的精度。对于这种类型的量化,您需要提供一个代表性数据集来估计激活的范围。让我们看下面的示例:
import tensorflow as tf
# A set of data for estimating the range of numbers that the inference requires
representative_dataset = …
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8 # or tf.uint8
converter.inference_output_type = tf.int8 # or tf.uint8
tflite_quant_model = converter.convert()
上述代码几乎是自说明的。再次使用TFLiteConverter
类进行量化。首先,我们配置优化策略(converter.optimizations = [tf.lite.Optimize.DEFAULT]
),并提供一个代表性数据集(converter.representative_dataset = representative_dataset
)。接下来,我们设置 TF 优化以进行整数表示。此外,我们还需要通过配置target_spec
、inference_input_type
和inference_output_type
来指定输入和输出数据类型。最后一行的convert
函数触发量化过程。
TF 中的两种后训练量化类型被详细解释在www.tensorflow.org/model_optimization/guide/quantization/post_training
。
接下来,我们将看看 PyTorch 如何实现后训练量化。
在 PyTorch 中执行后训练量化
对于 PyTorch,在后训练量化中有两种不同的方法:动态量化和静态量化。它们的区别在于量化发生的时间点,并且具有不同的优缺点。在本节中,我们将为每种算法提供高级描述以及代码示例。
动态量化 - 在运行时对模型进行量化
首先,我们将详细介绍动态量化,在 PyTorch 中是可用的最简单形式的量化。这种类型的算法在权重上提前应用量化,而在推断期间动态进行激活的量化。因此,动态量化通常用于模型执行主要受加载权重限制的情况,而计算矩阵乘法不是问题。这种类型的量化通常用于 LSTM 或 Transformer 网络。
给定训练好的模型,可以按如下方式实现动态量化。完整示例可在pytorch.org/tutorials/recipes/recipes/dynamic_quantization.html
找到:
import torch
model = …
quantized_model = torch.quantization.quantize_dynamic(
model, # the original model
qconfig_spec={torch.nn.Linear}, # a set of layers to quantize
dtype=torch.qint8) # data type which the quantized tensors will be
要应用动态量化,您需要将训练好的模型传递给torch.quantization.quantize_dynamic
函数。另外两个参数分别指定要应用量化的模块集(qconfig_spec={torch.nn.Linear}
)和量化张量的目标数据类型(dtype=torch.qint8
)。在此示例中,我们将Linear
层量化为 8 位整数。
接下来,让我们看一下静态量化。
静态量化 - 使用代表性数据集确定最佳量化参数
另一种量化方法称为静态量化。像 TF 的完全整数量化一样,这种量化通过使用代表性数据集估计模型与之交互的数字范围,以最小化模型性能下降。
不幸的是,静态量化比动态量化需要更多的编码。首先,您需要在网络前后插入torch.quantization.QuantStub
和torch.quantization.DeQuantStub
操作,以进行必要的张量转换:
import torch
# A model with few layers
class OriginalModel(torch.nn.Module):
def __init__(self):
super(M, self).__init__()
# QuantStub converts the incoming floating point tensors into a quantized tensor
self.quant = torch.quantization.QuantStub()
self.linear = torch.nn.Linear(10, 20)
# DeQuantStub converts the given quantized tensor into a tensor in floating point
self.dequant = torch.quantization.DeQuantStub()
def forward(self, x):
# using QuantStub and DeQuantStub operations, we can indicate the region for quantization
# point to quantized in the quantized model
x = self.quant(x)
x = self.linear(x)
x = self.dequant(x)
return x
在上述网络中,我们有一个单独的Linear
层,但在__init__
函数中还有两个额外的操作初始化:torch.quantization.QuantStub
和torch.quantization.DeQuantStub
。前者用于输入张量以指示量化的开始。后者作为forward
函数中的最后一个操作以指示量化的结束。以下代码片段描述了静态量化的第一步 - 校准过程:
# model is instantiated and trained
model_fp32 = OriginalModel()
…
# Prepare the model for static quantization
model_fp32.eval()
model_fp32.qconfig = torch.quantization.get_default_qconfig('fbgemm')
model_fp32_prepared = torch.quantization.prepare(model_fp32)
# Determine the best quantization settings by calibrating the model on a representative dataset.
calibration_dataset = …
model_fp32_prepared.eval()
for data, label in calibration_dataset:
model_fp32_prepared(data)
上述代码片段以训练好的模型model_fp32
开头。为了将模型转换为用于校准过程的中间格式,您需要附加一个量化配置(model_fp32.qconfig
)并将模型传递给torch.quantization.prepare
方法。如果模型推断在服务器实例上运行,则必须将模型的qconfig
属性设置为torch.quantization.get_default_qconfig('fbgemm')
。如果目标环境是移动设备,则必须向get_default_qconfig
函数传入'qnnpack'
。通过将代表性数据集传递给生成的模型model_fp32_prepared
,可以实现校准过程。
最后一步是将校准模型转换为量化模型:
model_int8 = torch.quantization.convert(model_fp32_prepared)
在上述代码行中,torch.quantization.convert
操作量化了校准模型(model_fp32_prepared
)并生成了模型的量化版本(model_int8
)。
关于静态量化的其他详细信息可在pytorch.org/tutorials/advanced/static_quantization_tutorial.html
找到。
在接下来的部分,我们将描述如何在 TF 和 PyTorch 中执行量化感知训练。
执行量化感知训练
后训练量化可以显著减少模型大小。然而,它可能会大幅降低模型的准确性。因此,以下问题产生了:我们能否恢复部分丢失的准确性?这个问题的答案可能是量化感知训练(QAT)。在这种情况下,模型在训练之前被量化,以便可以直接使用较低精度的权重和激活进行泛化学习。
首先,让我们看看如何在 TF 中实现这一点。
TensorFlow 中的量化感知训练
TF 通过 TensorFlow Model Optimization Toolkit 提供量化感知训练(QAT)。以下代码片段描述了如何在 TF 中设置 QAT:
import tensorflow_model_optimization as tfmot
# A TF model
model = …
q_aware_model = tfmot.quantization.keras.quantize_model(model)
q_aware_model.compile(
optimizer=...,
loss=...,
metrics=['accuracy'])
q_aware_model.fit(...)
如您所见,我们使用了 tfmot.quantization.keras.quantize_model
函数来设置 QAT 模型。输出模型需要使用 compile
函数进行编译,并可以使用 fit
函数进行训练,就像普通 TF 模型一样。令人惊讶的是,这就是您所需的全部。训练过的模型已经被量化,并应该提供比后训练量化生成的模型更高的准确性。
欲了解更多详情,请参阅原始文档:www.tensorflow.org/model_optimization/guide/quantization/training_comprehensive_guide
。
接下来,我们将看一下 PyTorch 的情况。
PyTorch 中的量化感知训练
在 PyTorch 中,QAT 经历了类似的过程。在训练过程中,会对必要的计算进行处理,这些计算会被夹紧和四舍五入,以模拟量化效果。完整的细节可以在 pytorch.org/docs/stable/quantization.html#quantization-aware-training-for-static-quantization
找到。让我们看看如何为 PyTorch 模型设置 QAT。
设置 QAT 的过程几乎与我们在“静态量化 - 使用代表性数据集确定最佳量化参数”部分所经历的过程相同。对于静态量化和 QAT,模型都需要进行相同的修改;需要在模型定义中插入 torch.quantization.QuantStub
和 torch.quantization.DeQuantStub
操作,以指示量化区域。主要区别来自网络的中间表示,因为 QAT 包括在整个训练过程中更新模型参数。以下代码片段更好地描述了这种差异:
model_fp32 = OriginalModel()
# model must be set to train mode for QAT
model_fp32.train()
model_fp32.qconfig = torch.quantization.get_default_qat_qconfig('fbgemm')
model_fp32_prepared = torch.quantization.prepare_qat(model_fp32_fused)
# train the model
for data, label in train_dataset:
pred = model_fp32_prepared(data)
...
# Generate quantized version of the trained model
model_fp32_prepared.eval()
model_int8 = torch.quantization.convert(model_fp32_prepared)
在前面的示例中,我们使用了在Static quantization – determining optimal quantization parameters using a representative dataset部分定义的相同网络:OriginalModel
。模型应处于 QAT(model_fp32.train()
)的train
模式。在这里,我们假设模型将部署在服务器实例上:torch.quantization.get_default_qat_qconfig('fbgemm')
。在 QAT 的情况下,模型的中间表示是通过将原始模型传递给 torch.quantization.prepare_qat
函数来创建的。您需要训练中间表示(model_fp32_prepared
)而不是原始模型(model_fp32
)。完成训练后,您可以使用 torch.quantization.convert
函数生成量化模型。
总体而言,我们研究了 TF 和 PyTorch 如何提供 QAT 以最小化量化对模型精度的降低。
需记住的事项
a. 网络量化是一种简单的技术,通过将处理数字的精度降低来减少推断延迟。
b. 有两种类型的网络量化:后训练量化,将量化应用于已经训练好的模型,以及 QAT,通过低精度训练模型来最小化精度降低。
c. TF 和 PyTorch 支持在训练代码中进行最小修改的后训练量化和 QAT。
在接下来的部分中,我们将看看另一种改善推断延迟的选项:权重共享。
权重共享 - 减少不同权重值的数量
权重共享或权重聚类是另一种可以显著减小模型大小的技术。这种技术背后的想法相当简单:让我们将权重聚合成组(或簇),并使用中心值而不是单独的权重值。在这种情况下,我们可以仅存储每个中心点的值,而不是每个权重值。因此,我们可以显著压缩模型大小,并可能加快推断过程。权重共享的关键思想在Figure 10.2中有图形化展示(改编自官方 TF 博客文章关于权重聚类 API: blog.tensorflow.org/2020/08/tensorflow-model-optimization-toolkit-weight-clustering-api.html
):
图 10.2 - 权重共享的示例插图
让我们先学习如何在 TF 中执行权重共享,然后再看如何在 PyTorch 中执行相同操作。
在 TensorFlow 中执行权重共享
TF 提供了针对 Sequential
和 Functional
TF 模型的权重共享,通过 TensorFlow Model Optimization Toolkit (www.tensorflow.org/model_optimization/guide/clustering/clustering_example
) 实现。
首先,您需要定义如下代码片段所示的聚类配置:
import tensorflow_model_optimization as tfmot
# A trained model to compress
tf_model = ...
CentroidInitialization = tfmot.clustering.keras.CentroidInitialization
clustering_params = {
'number_of_clusters': 10,
'cluster_centroids_init': CentroidInitialization.LINEAR
}
clustered_model = tfmot.clustering.keras.cluster_weights(tf_model, **clustering_params)
如您所见,权重聚类涉及tfmot.clustering.keras.cluster_weights
函数。 我们需要提供训练好的模型(tf_model
)和一个聚类配置(clustering_params
)。 聚类配置定义了集群的数量以及如何初始化每个集群。 在此示例中,我们生成了 10 个集群,并使用线性质心初始化(集群质心将均匀分布在最小值和最大值之间)。 可以在www.tensorflow.org/model_optimization/api_docs/python/tfmot/clustering/keras/CentroidInitialization
找到其他集群初始化选项。
生成带有聚类权重的模型后,您可以使用tfmot.clustering.keras.strip_clustering
函数删除推断期间不需要的所有变量:
final_model = tfmot.clustering.keras.strip_clustering(clustered_model)
接下来,我们将看看如何在 PyTorch 中执行权重共享。
在 PyTorch 中执行权重共享
不幸的是,PyTorch 不支持权重共享。 相反,我们将提供可能实现的高级描述。 在此示例中,我们将尝试实现描述在Figure 10.2中的操作。 首先,在模型实现中添加一个名为cluster_weights
的自定义函数,您可以在训练后调用该函数以对权重进行聚类。 然后,forward
方法将需要稍作修改,如下面的代码片段所述:
from torch.nn import Module
class SampleModel(Module):
# in the case of PyTorch Lighting, we inherit pytorch_lightning.LightningModule class
def __init__(self):
self.layer = …
self.weights_cluster = … # cluster index for each weight
self.weights_mapping = … # mapping from a cluster index to a centroid value
def forward(self, input):
if self.training: # in training mode
output = self.layer(input)
else: # in eval mode
# update weights of the self.layer by reassigning each value based on self.weights_cluster and self.weights_mapping
output = self.layer(input)
return output
def cluster_weights(self):
# cluster weights of the layer
# construct a mapping from a cluster index to a centroid value and store at self.weights_mapping
# find cluster index for each weight value and store at self.weights_cluster
# drop the original weights to reduce the model size
# First, we instantiate a model to train
model = SampleModel()
# train the model
…
# perform weight sharing
model.cluster_weights()
model.eval()
前面的代码应该是自解释的,因为它是带有注释的伪代码,解释了关键操作。 首先,模型被训练,就像是正常模型一样。 当触发cluster_weights
函数时,权重被聚类,并且权重共享所需的信息存储在类内部;每个权重的集群索引存储在self.weights_cluster
中,并且每个集群的质心值存储在self.weights_mapping
中。 当模型处于eval
模式时,forward
操作使用从self.weights_cluster
和self.weights_mapping
构建的不同权重集。 另外,您可以添加功能以丢弃部署期间不需要的现有权重以减小模型大小。 我们在我们的存储库中提供了完整的实现:github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/blob/main/Chapter_10/weight_sharing_pytorch.ipynb
.
记住的事情
a. 权重共享通过将不同的权重值分组并用质心值替换来减小模型大小。
b. TF 通过 TensorFlow Model Optimization Toolkit 提供了权重共享,但 PyTorch 不提供任何支持。
接下来,让我们学习另一种流行的技术,称为网络修剪。
网络修剪 - 消除网络内不必要的连接
网络修剪是一种优化过程,可以消除不必要的连接。这种技术可以在训练后应用,但也可以在训练期间应用,从而进一步减少模型精度下降。连接更少意味着需要的权重更少。因此,我们可以减小模型大小以及推断延迟。在接下来的章节中,我们将介绍如何在 TF 和 PyTorch 中应用网络修剪。
希望这个翻译能够满足你的要求!
像模型量化和权重共享一样,TF 的网络修剪可以通过 TensorFlow 模型优化工具包实现。因此,进行网络修剪的第一步是使用以下代码行导入该工具包:
import tensorflow_model_optimization as tfmot
在训练过程中应用网络修剪,您必须使用tfmot.sparsity.keras.prune_low_magnitude
函数修改您的模型:
# data and configurations for training
x_train, y_train, x_text, y_test, x_valid, y_valid, num_examples_train, num_examples_test, num_examples_valid = …
batch_size = ...
end_step = np.ceil(num_examples_train / batch_size).astype(np.int32) * epochs
# pruning configuration
pruning_params = {
'pruning_schedule': tfmot.sparsity.keras.PolynomialDecay(initial_sparsity=0.3,
final_sparsity=0.5,
begin_step=0,
end_step=end_step)}
# Prepare a model that will be pruned
model = ...
model_for_pruning = tfmot.sparsity.keras.prune_low_magnitude(model, **pruning_params)
在前述代码中,我们通过为prune_low_magnitude
函数提供模型和一组参数pruning_params
来配置网络修剪。正如您所见,我们应用了PolynomialDecay
修剪,该修剪通过在训练过程中从特定稀疏性(initial_sparsity
)开始构建到目标稀疏性的网络(www.tensorflow.org/model_optimization/api_docs/python/tfmot/sparsity/keras/PolynomialDecay
)。正如最后一行所示,prune_low_magnitude
函数返回另一个在训练期间执行网络修剪的模型。
在我们查看需要进行训练循环修改之前,我们想介绍另一种修剪配置,即tfmot.sparsity.keras.ConstantSparsity
(www.tensorflow.org/model_optimization/api_docs/python/tfmot/sparsity/keras/ConstantSparsity
)。该修剪配置通过整个训练过程应用恒定稀疏性修剪。要应用此类型的网络修剪,您可以简单地按照以下代码片段修改pruning_params
:
pruning_params = {
'pruning_schedule': tfmot.sparsity.keras.ConstantSparsity(0.5, begin_step=0, frequency=100) }
如下代码片段所示,训练循环需要进行一项额外的修改以进行回调配置;我们需要使用一个 Keras 回调函数,该函数对每个优化器步骤应用修剪 - 即tfmot.sparsity.keras.UpdatePruningStep
:
model_for_pruning.compile(…)
callbacks = [tfmot.sparsity.keras.UpdatePruningStep()]
model_for_pruning.fit(x_train, y_train,
batch_size=batch_size, epochs=epochs, validation_data=(x_valid, y_vallid),
callbacks=callbacks)
前述代码编译了已准备好进行网络修剪和进行训练的模型。请记住,关键变化来自于为fit
函数指定的tfmot.sparsity.keras.UpdatePruningStep
回调函数。
最后,你可以通过将模型传递到 tfmot.sparsity.keras.strip_pruning
函数来更新训练过的模型,以仅保留稀疏权重。所有不必要用于模型推理的 tf.Variable
实例都将被丢弃:
final_tf_model = tfmot.sparsity.keras.strip_pruning(model_for_pruning)
提供的示例可以直接应用于 Functional
和 Sequential
TF 模型。要对特定层或模型子集应用修剪,需要进行以下修改:
def apply_pruning_to_dense(layer):
if isinstance(layer, tf.keras.layers.Dense):
return tfmot.sparsity.keras.prune_low_magnitude(layer)
return layer
model_for_pruning = tf.keras.models.clone_model(model, clone_function=apply_pruning_to_dense)
首先,我们定义了一个 apply_pruning_to_dense
包装函数,将 prune_low_magnitude
函数应用于目标层。然后,我们只需将原始模型和 apply_pruning_to_dense
函数传递给 tf.keras.models.clone_model
函数,该函数通过在给定模型上运行提供的函数来生成新模型。
值得一提的是,存在 tfmot.sparsity.keras.PrunableLayer
抽象类,专为自定义网络修剪而设计。关于此类的更多详细信息,请参见 www.tensorflow.org/model_optimization/api_docs/python/tfmot/sparsity/keras/PrunableLayer
和 www.tensorflow.org/model_optimization/guide/pruning/comprehensive_guide#custom_training_loop
。
接下来,我们将看一下如何在 PyTorch 中进行修剪。
PyTorch 中的网络修剪
PyTorch 通过 torch.nn.utils.prune
模块支持训练后的网络修剪。给定一个训练好的网络,可以通过将模型传递给 global_unstructured
函数来实现修剪。一旦模型被修剪,就会附加一个二进制掩码,该掩码表示被修剪的参数集合。在 forward
操作之前,掩码被应用于目标参数,从而消除不必要的计算。让我们看一个例子:
# model is instantiated and trained
model = …
parameters_to_prune = (
(model.conv, 'weight'),
(model.fc, 'weight')
)
prune.global_unstructured(
parameters_to_prune,
pruning_method=prune.L1Unstructured, # L1-norm
amount=0.2
)
如前面的代码片段所示,global_unstructured
函数的第一个参数定义了将应用修剪的网络组件 (parameters_to_prune
)。第二个参数定义了修剪算法 (pruning_method
)。最后一个参数 amount
表示要修剪的参数百分比。在本例中,我们基于 L1 范数修剪了连接的最低 20%。如果你对其他算法感兴趣,你可以在 pytorch.org/docs/stable/nn.html#utilities
找到完整列表。
PyTorch 还支持每层修剪以及迭代修剪。你还可以定义一个自定义修剪算法。关于上述功能的详细信息可以在 https://pytorch.org/tutorials/intermediate/pruning_tutorial.html#pruning-tutorial 找到。
需要记住的事情
a. 网络修剪是通过消除网络中不必要的连接来减小模型大小的优化过程。
b. TensorFlow 和 PyTorch 都支持模型级和层级的网络修剪。
在本节中,我们描述了如何消除网络中的不必要连接以提高推理延迟。在下一节中,我们将学习一种名为知识蒸馏的技术,该技术生成一个新模型而不是修改现有模型。
知识蒸馏 – 通过模仿预测获得较小的网络
知识蒸馏的概念最早由希尔顿等人在其题为Distilling the Knowledge in a Neural Network的出版物中于 2015 年首次引入。在分类问题中,Softmax 激活通常作为网络的最后操作,以将每个类别的置信度表示为概率。由于最高概率的类别用于最终预测,因此其他类别的概率被认为不重要。然而,作者认为它们仍包含有意义的信息,代表了模型对输入的解释。例如,如果两个类别在多个样本中报告类似的概率,则这两个类别可能具有许多共同的特征,使得区分两者变得困难。当网络较深时,这些信息变得更加丰富,因为它可以从其已见过的数据中提取更多信息。基于这一思想,作者提出了一种将已训练模型的知识转移到较小尺寸模型的技术:知识蒸馏。
知识蒸馏的过程通常被称为老师与学生分享知识;原始模型称为老师模型,而较小的模型称为学生。如下图所示,学生模型从单个输入构建的两个不同标签中进行训练。一个标签是地面实况标签,称为硬标签。另一个标签称为软标签。软标签是老师模型的输出概率。知识蒸馏的主要贡献来自软标签填补硬标签中缺失信息的能力:
图 10.3 – 知识蒸馏过程概述
从许多评估知识蒸馏好处的实验中可以证明,使用较小的网络可以达到可比较的性能。令人惊讶的是,在某些情况下,更简单的网络结构导致正则化,并使学生模型表现优于教师模型。
自该技术首次出现以来,已经引入了许多变体。第一组变体来自于如何定义知识:基于响应的知识(网络输出),基于特征的知识(中间表示),以及基于关系的知识(层次或数据样本之间的关系)。另一组变体集中在如何实现知识传输上:离线蒸馏(从预训练的教师模型训练学生模型),在线蒸馏(在两个模型训练时共享知识),以及自蒸馏(在单个网络内部共享知识)。如果你希望进一步探索这个领域,我们认为由 Gou 等人撰写的名为知识蒸馏:一项调查的论文可能是一个很好的起点。
由于训练设置的复杂性,目前没有一个直接支持知识蒸馏的框架。然而,如果模型网络复杂而输出结构简单,这仍然是一个很好的选择。
要记住的事情
a. 知识蒸馏是一种将训练模型的知识转移到较小模型的技术。
b. 在知识蒸馏中,原始模型称为教师模型,而较小模型称为学生模型。学生模型从两个标签进行训练:地面真实标签和教师模型的输出。
最后,我们介绍了一种修改网络架构以减少模型参数数量的技术:网络架构搜索。
网络架构搜索 – 寻找最有效的网络架构
**神经架构搜索(NAS)**是为给定问题找到最佳层次结构的过程。由于可能的网络架构搜索空间极其庞大,评估每种可能的网络架构是不可行的。因此,需要一种聪明的方法来识别有前途的网络架构并评估候选者。因此,NAS 方法从三个不同的方面发展:
-
搜索空间: 如何构建一个合理大小的搜索空间
-
搜索策略: 如何高效探索搜索空间
-
性能估算策略: 如何在不完全训练模型的情况下有效估算性能
尽管 NAS 是一个快速发展的研究领域,但针对 TF 和 PyTorch 模型仅有少量工具可用:
-
Optuna (
dzlab.github.io/dltips/zh/tensorflow/hyperoptim-optuna
) -
Syne-Tune,可以与 SageMaker 一起使用 (
aws.amazon.com/blogs/machine-learning/run-distributed-hyperparameter-and-neural-architecture-tuning-jobs-with-syne-tune
) -
Katib (
www.kubeflow.org/docs/components/katib/hyperparameter
), -
SigOpt (
sigopt.com/blog/simple-neural-architecture-search-nas-intel-sigopt
)
简化版 NAS 实现包括从随机层次组织定义搜索空间。然后,我们简单选择表现最佳的模型。为了减少总体搜索时间,我们可以根据特定评估指标应用早停,这将在评估指标不再改变时快速停止训练。这样的设置将 NAS 重新构造为一个超参数调整问题,其中模型架构已成为一个参数。我们可以通过应用以下技术之一进一步改进搜索算法:
-
贝叶斯优化
-
强化学习 (RL)
-
基于梯度的方法
-
基于层次的方法
如果您想进一步探索这个领域,我们建议自己实施 NAS。首先,您可以利用在第七章中介绍的超参数调整技术。您可以从随机参数搜索或结合早停的贝叶斯优化方法开始。然后,我们建议研究基于 RL 的实现。我们还建议阅读一篇名为神经架构搜索综述:挑战与解决方案的论文,作者是任鹏振等人。
要记住的事情
a. NAS 是找到解决方案的最佳网络架构的过程。
b. NAS 包括三个组成部分:搜索空间、搜索策略和性能估计策略。它涉及评估不同架构的网络并找到最佳架构。
c. NAS 的几个工具包括:Optuna、Syne-Tune、Katib、NNI 和 SigOpt。
在本节中,我们介绍了 NAS 及其如何生成更小网络的方式。
概述
在本章中,我们介绍了一系列技术,可以通过减少模型大小来改善推理延迟。我们介绍了三种最流行的技术,以及在 TF 和 PyTorch 中的完整示例:网络量化、权重共享和网络修剪。我们还描述了通过直接修改网络架构来减少模型大小的技术:知识蒸馏和 NAS。
在下一章中,我们将解释如何在移动设备上部署 TF 和 PyTorch 模型,在这一节描述的技术将会很有用。
第十一章:移动设备上的深度学习
本章将介绍如何在移动设备上部署深度学习(DL)模型,这些模型是使用TensorFlow(TF)和PyTorch开发的,并使用TensorFlow Lite(TF Lite)和PyTorch Mobile分别进行部署。首先,我们将讨论如何将 TF 模型转换为 TF Lite 模型。然后,我们将解释如何将 PyTorch 模型转换为 TorchScript 模型,以便 PyTorch Mobile 可以使用。最后,本章的最后两节将涵盖如何将转换后的模型集成到 Android 和 iOS 应用程序(应用)中。
本章中,我们将涵盖以下主要主题:
-
为移动设备准备 DL 模型
-
使用 DL 模型创建 iOS 应用程序
-
使用 DL 模型创建 Android 应用程序
为移动设备准备 DL 模型
移动设备通过便捷地访问互联网改变了我们日常生活的进行方式;我们许多日常任务都严重依赖移动设备。因此,如果我们能在移动应用中部署 DL 模型,我们应该能够实现更高水平的便利。流行的用例包括不同语言之间的翻译、目标检测和数字识别等。
以下截图展示了一些示例用例:
图 11.1 - 从左到右,列出的应用程序处理植物识别、目标检测和机器翻译,利用 DL 的灵活性
移动设备存在许多操作系统(OSs)。然而,目前两种 OSs 在移动市场占据主导地位:iOS 和 Android。iOS 是苹果设备(如 iPhone 和 iPad)的操作系统。同样,Android 是由三星和谷歌等公司生产的设备的标准操作系统。在本章中,我们专注于针对这两种主导 OSs 的部署。
不幸的是,TF 和 PyTorch 模型不能直接在移动设备上部署。我们需要将它们转换为可以在移动设备上运行推断逻辑的格式。对于 TF,我们需要一个 TF Lite 模型;我们将首先讨论如何使用tensorflow
库将 TF 模型转换为 TF Lite 模型。另一方面,PyTorch 涉及 PyTorch Mobile 框架,该框架只能消耗 TorchScript 模型。在讨论了 TF Lite 转换后,我们将学习如何将 PyTorch 模型转换为 TorchScript 模型。此外,我们还将解释如何优化 PyTorch 模型的特定层,以适应目标移动环境。
值得注意的是,TF 模型或 PyTorch 模型可以转换为开放神经网络交换(ONNX)运行时,并部署到移动设备上(onnxruntime.ai/docs/tutorials/mobile
)。此外,SageMaker 提供了内置支持,可将 DL 模型加载到边缘设备上:SageMaker Edge Manager(docs.aws.amazon.com/sagemaker/latest/dg/edge-getting-started-step4.html
)。
生成 TF Lite 模型
TF Lite 是一个用于在移动设备、微控制器和其他边缘设备上部署模型的库(www.tensorflow.org/lite
)。训练好的 TF 模型需要转换为 TF Lite 模型,才能在边缘设备上运行。如下面的代码片段所示,tensorflow
库内置支持将 TF 模型转换为 TF Lite 模型(.tflite
文件):
import tensorflow as tf
# path to the trained TF model
trained_model_dir = "s3://mybucket/tf_model"
# TFLiteConverter class is necessary for the conversion
converter = tf.lite.TFLiteConverter.from_saved_model(trained_model_dir)
tfl_model = converter.convert()
# save the converted model to TF Lite format
with open('model_name.tflite', 'wb') as f:
f.write(tfl_model)
在上述 Python 代码中,tf.lite.TFLiteConverter
类的 from_saved_model
函数加载训练好的 TF 模型文件。该类的 convert
方法将加载的 TF 模型转换为 TF Lite 模型。
如第十章讨论的那样,提升推理效率,TF Lite 支持各种模型压缩技术。从 TF Lite 中流行的技术包括网络剪枝和网络量化。
接下来,让我们看一下如何将 PyTorch 模型转换为 TorchScript 模型以用于 PyTorch Mobile。
生成 TorchScript 模型
可以使用 PyTorch Mobile 框架在移动设备上运行 PyTorch 模型(pytorch.org/mobile/home/
)。类似于 TF 的情况,必须将训练好的 PyTorch 模型转换为 TorchScript 模型,以便使用 PyTorch Mobile 运行模型(pytorch.org/docs/master/jit.html
)。TorchScript 模块的主要优势在于能够在 Python 以外的环境(如 C++ 环境)中运行 PyTorch 模块。torch.jit.script
方法将给定 DL 模型的图导出为低级表示,可以在 C++ 环境中执行。有关跨语言支持的完整细节,请参阅pytorch.org/docs/stable/jit_language_reference.html#language-reference
。请注意,TorchScript 目前仍处于 beta 状态。
要从 PyTorch 模型获取 TorchScript 模型,需要将训练好的模型传递给 torch.jit.script
函数,如下面的代码片段所示。可以通过 torch.utils.mobile_optimizer
模块的 optimize_for_mobile
方法来进一步优化 TorchScript 模型,以适应移动环境,例如融合 Conv2D
和 BatchNorm
层或者移除不必要的 Dropout
层(详情请参考 pytorch.org/docs/stable/mobile_optimizer.html
)。请注意,mobile_optimizer
方法目前仍处于 beta 状态。
import torch
from torch.utils.mobile_optimizer import optimize_for_mobile
# load a trained PyTorch model
saved_model_file = "model.pt"
model = torch.load(saved_model_file)
# the model should be in evaluate mode for dropout and batch normalization layers
model.eval()
# convert the model into a TorchScript model and apply optimization for mobile environment
torchscript_model = torch.jit.script(model)
torchscript_model_optimized = optimize_for_mobile(torchscript_model)
# save the optimized TorchScript model into a .pt file
torch.jit.save(torchscript_model_optimized, "mobile_optimized.pt")
在上述示例中,我们首先将训练好的模型加载到内存中(torch.load("model.pt")
)。模型在进行转换时应处于 eval
模式。接下来,我们使用 torch.jit.script
函数将 PyTorch 模型转换为 TorchScript 模型(torchscript_model
)。使用 optimize_for_mobile
方法进一步优化 TorchScript 模型,生成优化后的 TorchScript 模型(torch_script_model_optimized
)。最后,可以使用 torch.jit.save
方法将优化后的 TorchScript 模型保存为独立的 .pt
文件(mobile_optimized.pt
)。
注意事项
a. 在移动设备上运行 TF 模型涉及 TF Lite 框架。训练好的模型需要转换成 TF Lite 模型。使用 tensorflow.lite
库中的 TFLiteConverter
类来进行转换。
b. 在移动设备上运行 PyTorch 模型涉及 PyTorch Mobile 框架。鉴于 PyTorch Mobile 仅支持 TorchScript 模型,需要使用 torch.jit 库将训练好的模型转换为 TorchScript
模型。
接下来,我们将学习如何将 TF Lite 和 TorchScript 模型集成到 iOS 应用中。
使用 DL 模型创建 iOS 应用
在本节中,我们将讨论如何为 iOS 应用编写 TF Lite 和 TorchScript 模型的推断代码。虽然 Swift 和 Objective-C 是 iOS 的本地语言,可以在一个项目中同时使用,但我们主要关注 Swift 的用例,因为它比 Objective-C 更受欢迎。
如果我们详细解释 iOS 应用开发的每一个步骤,本章将会很冗长。因此,我们将基础内容放在了苹果提供的官方教程中:developer.apple.com/tutorials/app-dev-training
。
在 iOS 上运行 TF Lite 模型推断
在本节中,我们展示了如何在 iOS 应用程序中加载 TF Lite 模型,使用TensorFlowLiteSwift
,这是 TF Lite 的 iOS 本地库(github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/swift
)。可以通过 CocoaPods 安装TensorFlowLiteSwift
,这是 iOS 应用程序开发的标准包管理器(cocoapods.org
)。要在 macOS 上下载 CocoaPods,可以在终端上运行brew install cocoapods
命令。每个 iOS 应用程序开发都涉及一个 Podfile,列出了应用程序开发所依赖的库。必须将TensorFlowLiteSwift
库添加到此文件中,如以下代码片段所示:
pod 'TensorFlowLiteSwift'
要在 Podfile 中安装所有库,可以运行pod install
命令。
下面的步骤描述了如何在您的 iOS 应用程序中加载 TF Lite 模型并运行推理逻辑。有关执行的完整细节,请参阅www.tensorflow.org/lite/guide/inference#load_and_run_a_model_in_swift
:
-
可以使用
import
关键字加载安装的库:import TensorFlowLite
-
通过提供输入 TF Lite 模型的路径来初始化
Interpreter
类:let interpreter = try Interpreter(modelPath: modelPath)
-
为了将输入数据传递给模型,您需要使用
self.interpreter.copy
方法将输入数据复制到索引为0
的输入Tensor
对象中:let inputData: Data inputData = ... try self.interpreter.copy(inputData, toInputAt: 0)
-
一旦输入的
Tensor
对象准备好,就可以使用self.interpreter.invoke
方法运行推理逻辑:try self.interpreter.invoke()
-
可以使用
self.interpreter.output
检索生成的输出,作为可以进一步使用UnsafeMutableBufferPointer
类反序列化为数组的Tensor
对象:let outputTensor = try self.interpreter.output(at: 0) let outputSize = outputTensor.shape.dimensions.reduce(1, {x, y in x * y}) let outputData = UnsafeMutableBufferPointer<Float32>.allocate(capacity: outputSize) outputTensor.data.copyBytes(to: outputData)
在本节中,我们学习了如何在 iOS 应用程序中运行 TF Lite 模型推理。接下来,我们将介绍如何在 iOS 应用程序中运行 TorchScript 模型推理。
在 iOS 上运行 TorchScript 模型推理
在这一节中,我们将学习如何在 iOS 应用程序上使用 PyTorch Mobile 部署 TorchScript 模型。我们将从使用TorchModule
模块加载训练好的 TorchScript 模型的 Swift 代码片段开始。您需要用于 PyTorch Mobile 的库称为LibTorch_Lite
。该库也可通过 CocoaPods 获得。您只需将以下行添加到 Podfile 中:
pod 'LibTorch_Lite', '~>1.10.0'
如上一节所述,您可以运行pod install
命令来安装库。
鉴于 TorchScript 模型是为 C++ 设计的,Swift 代码不能直接运行模型推断。为了弥合这一差距,存在 TorchModule
类,它是 torch::jit::mobile::Module
的 Objective-C 包装器。要在应用程序中使用此功能,需要在项目下创建一个名为 TorchBridge
的文件夹,其中包含 TorchModule.mm
(Objective-C 实现文件)、TorchModule.h
(头文件)和一个命名约定为 -Bridging-Header.h
后缀的桥接头文件(以允许 Swift 加载 Objective-C 库)。完整的示例设置可以在 github.com/pytorch/ios-demo-app/tree/master/HelloWorld/HelloWorld/HelloWorld/TorchBridge
找到。
在接下来的步骤中,我们将展示如何加载 TorchScript 模型并触发模型预测:
-
首先,您需要将
TorchModule
类导入到项目中:#include "TorchModule.h"
-
接下来,通过提供 TorchScript 模型文件的路径来实例化
TorchModule
:let modelPath = "model_dir/torchscript_model.pt" let module = TorchModule(modelPath: modelPath)
-
TorchModule
类的predict
方法处理模型推断。需要向predict
方法提供输入,然后将返回输出。在幕后,predict
方法将通过 Objective-C 包装器调用模型的forward
函数。以下代码中有所示:let inputData: Data inputData = ... let outputs = module.predict(input: UnsafeMutableRawPointer(&inputData))
如果您对推断的幕后实际工作原理感兴趣,建议阅读 pytorch.org/mobile/ios/
中的 Run inference 部分。
需要记住的事情
a. Swift 和 Objective-C 是开发 iOS 应用程序的标准语言。一个项目可以包含用这两种语言编写的文件。
b. TensorFlowSwift
库是 Swift 的 TF 库。Interpreter
类支持 iOS 上 TF Lite 模型的推断。
c. LibTorch_Lite
库通过 TorchModule
类支持在 iOS 应用程序上进行 TorchScript 模型推断。
接下来,我们将介绍如何在 Android 上运行 TF Lite 和 TorchScript 模型的推断。
使用 DL 模型创建 Android 应用程序
在本节中,我们将讨论 Android 如何支持 TF Lite 和 PyTorch Mobile。Java 和 Java 虚拟机(JVM)为 Android 应用程序提供的首选语言(例如 Kotlin)。在本节中,我们将使用 Java。有关 Android 应用程序开发的基础知识,请访问 developer.android.com
。
我们首先专注于使用 org.tensorflow:tensorflow-lite-support
库在 Android 上运行 TF Lite 模型推断。然后,我们讨论如何使用 org.pytorch:pytorch_android_lite
库运行 TorchScript 模型推断。
在 Android 上运行 TF Lite 模型推断
首先,让我们看看如何使用 Java 在 Android 上运行 TF Lite 模型。使用 org.tensorflow:tensorflow-lite-support
库可以在 Android 应用上部署 TF Lite 模型。该库支持 Java、C++(测试版)和 Swift(测试版)。支持的环境完整列表可在 github.com/tensorflow/tflite-support
找到。
Android 应用开发涉及 Gradle,这是一个管理依赖项的构建自动化工具 (gradle.org
)。每个项目都会有一个 .gradle
文件,该文件指定了使用基于 JVM 的语言(如 Groovy 或 Kotlin)的项目规范。在以下代码片段中,我们列出了项目在 dependencies
部分下依赖的库:
dependencies {
implementation 'org.tensorflow:tensorflow-lite-support:0.3.1'
}
在前面的 Groovy Gradle 代码中,我们已经指定了 org.tensorflow:tensorflow-lite-support
库作为我们的依赖项之一。可以在 docs.gradle.org/current/samples/sample_building_java_applications.html
找到一个示例 Gradle 文件。
在接下来的步骤中,我们将学习如何加载 TF Lite 模型并运行推理逻辑。有关此过程的完整详细信息可以在 www.tensorflow.org/lite/api_docs/java/org/tensorflow/lite/Interpreter
找到:
-
首先是导入包含用于 TF Lite 模型推理的
Interpreter
类的org.tensorflow.lite
库:import org.tensorflow.lite.Interpreter;
-
然后,我们可以通过提供模型路径来实例化
Interpreter
类:let tensorflowlite_model_path = "tflitemodel.tflite"; Interpreter = new Interpreter(tensorflowlite_model_path);
-
Interpreter
类的run
方法用于运行推理逻辑。它只接受一个input
类型为HashMap
的实例,并提供一个类型为HashMap
的output
实例:Map<> input = new HashMap<>(); Input = ... Map<> output = new HashMap<>(); interpreter.run(input, output);
在下一节中,我们将学习如何将 TorchScript 模型加载到 Android 应用中。
在 Android 上运行 TorchScript 模型推理
在本节中,我们将解释如何在 Android 应用中运行 TorchScript 模型。要在 Android 应用中运行 TorchScript 模型推理,您需要使用 org.pytorch:pytorch_android_lite
库提供的 Java 包装器。同样,您可以在 .gradle
文件中指定必需的库,如下面的代码片段所示:
dependencies {
implementation 'org.pytorch:pytorch_android_lite:1.11'
}
在 Android 应用中运行 TorchScript 模型推理可以通过以下步骤来实现。关键是使用来自 org.pytorch
库的 Module
类,该类在后台调用 C++ 函数进行推理(pytorch.org/javadoc/1.9.0/org/pytorch/Module.html
):
-
首先,您需要导入
Module
类:import org.pytorch.Module;
-
Module
类提供了一个load
函数,通过加载提供的模型文件创建一个Module
实例:let torchscript_model_path = "model_dir/torchscript_model.pt"; Module = Module.load(torchscript_model_path);
-
Module
实例的forward
方法用于运行推理逻辑并生成org.pytorch.Tensor
类型的输出:Tensor outputTensor = module.forward(IValue.from(inputTensor)).toTensor();
虽然前面的步骤涵盖了org.pytorch
模块的基本用法,您可以在官方文档中找到其他细节:pytorch.org/mobile/android
。
需要记住的事项
a. Java 和基于 JVM 的语言(例如 Kotlin)是 Android 应用程序的本地语言。
b. org.tensorflow:tensorflow-lite-support
库用于在 Android 上部署 TF Lite 模型。Interpreter
类实例的run
方法处理模型推断。
c. org.pytorch:pytorch_android_lite
库专为在 Android 应用程序中运行 TorchScript 模型而设计。Module
类的forward
方法处理推断逻辑。
完成了在 Android 上部署 DL 模型。现在,您应该能够将任何 TF 和 PyTorch 模型集成到 Android 应用程序中。
总结
在本章中,我们介绍了如何将 TF 和 PyTorch 模型集成到 iOS 和 Android 应用程序中。我们从描述从 TF 模型到 TF Lite 模型的必要转换以及从 PyTorch 模型到 TorchScript 模型开始本章。接下来,我们提供了加载 TF Lite 和 TorchScript 模型并在 iOS 和 Android 上使用加载模型进行推断的完整示例。
在下一章中,我们将学习如何关注部署模型。我们将查看一组用于模型监控的工具,并描述如何有效监控部署在亚马逊弹性 Kubernetes 服务(Amazon EKS)和 Amazon SageMaker 上的模型。