65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Azure 上大规模部署模型(第三部分):部署和扩展 TensorFlow 模型

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2022 年 3 月 30 日

CPOL

10分钟阅读

viewsIcon

5978

如何发布 TensorFlow 模型。

Azure 帮助运行几乎任何规模的机器学习 (ML) 工作负载,因此您的模型不再受限于您的计算机处理器。在本三部分系列的前几篇文章中,我们使用 Azure 应用服务、Flask、FastAPI 和机器学习在线终结点发布了 XGBoostPyTorch 模型。在本文中,我们将继续上一系列中的最后一个模型:一个 TensorFlow 模型。

我们的目标是为机器学习模型上的实时推理创建自定义 Rest API 服务。我们将使用经过训练以使用著名 MNIST 数据集识别手写数字的模型。

首先,我们将使用 Azure 机器学习在线终结点发布一个 TensorFlow 模型。然后,我们将创建一个 Azure 函数作为我们应用服务的公共代理,说明如何在代码中消费在线终结点。

我们依靠 Azure CLI 来编写易于理解和可重复的脚本,我们可以将其与其余代码一起存储和版本控制。在本文章的专用 GitHub 存储库中找到本文的示例代码、脚本、模型和一些测试图像。

入门

要遵循本文的示例,您需要 Visual Studio Code、最新的 Python 版本(3.7+)和用于 Python 包管理的 Conda。如果您没有其他偏好,请从 Miniconda 和 Python 3.9 开始。

安装 Conda 后,创建并激活新环境

$ conda create -n azureml python=3.9
$ conda activate azureml

除了 Python 和 Conda,我们还使用 2.15.0 或更新版本的 Azure 命令行工具 (azure-cli),并带有机器学习扩展

$ az extension add -n ml -y

最后但并非最不重要的一点是,如果您还没有 Azure 帐户,请注册免费的 Azure 帐户。您的新帐户包括免费积分和对各种服务的访问权限。

在所有这些资源都准备就绪后,登录到您的订阅。

$ az login

设置以下环境变量,我们将在脚本中使用它们

export AZURE_SUBSCRIPTION="<your-subscription-id>"
export RESOURCE_GROUP="azureml-rg"
export AML_WORKSPACE="demo-ws"
export LOCATION="westeurope"

如果您没有遵循上一系列中的示例,您还需要创建一个 Azure 机器学习工作区

$ az ml workspace create --name $AML_WORKSPACE --subscription $AZURE_SUBSCRIPTION 
  --resource-group $RESOURCE_GROUP --location $LOCATION

现在,我们准备好开始为模型部署做准备了。

注册模型

为了避免将模型与应用程序绑定和部署,我们使用 Azure 机器学习工作区中内置的模型注册表。如果您遵循了上一系列中的第三篇文章,您的 TensorFlow 模型可能已经存在。如果不是,请使用此命令

$ az ml model create --name "mnist-tf-model" --local-path "./mnist-tf-model.h5" 
  --subscription $AZURE_SUBSCRIPTION --resource-group $RESOURCE_GROUP 
  --workspace-name $AML_WORKSPACE

构建模型推理代码

与之前的文章一样,我们服务的核心部分将是加载模型并在图像数据上运行推理的代码。我们将代码存储在 `endpoint-code/inference_model.py` 文件中。

import numpy as np
import tensorflow as tf
from PIL import Image

class InferenceModel():
    def __init__(self, model_path):
        self.model = tf.keras.models.load_model(model_path)
 
    def _preprocess_image(self, image_bytes):
        image = Image.open(image_bytes)        
        image = image.resize((28,28)).convert('L')
        image_np = (255 - np.array(image.getdata())) / 255.0
 
        return image_np.reshape(-1,28,28,1)
 
    def predict(self, image_bytes):
        image_data = self._preprocess_image(image_bytes)
        prediction = self.model.predict(image_data)
 
        return np.argmax(prediction, axis=1)

与本系列前面的文章一样,此文件包含一个名为 `InferenceModel` 的类,它有三个方法:`__init__`、`_preprocess_image` 和 `predict`。

`__init__` 方法从文件中加载模型并将其存储以备后用。

`_preprocess_image` 方法调整图像大小并将其转换为可接受的格式。我们的 TensorFlow 模型接受一个单通道 28x28 张量,浮点值范围为 0.0 到 1.0。

请注意像素强度的反转。我们计划在标准黑白图像上使用我们的模型,而 MNIST 训练数据集具有反转的黑底白字值。

最终的 `predict` 方法对提供的图像数据运行推理。

创建在线终结点代码

要创建托管在线终结点,我们需要使用终结点的服务代码包装我们的 `InferenceModel` 类。我们将其存储在 `endpoint-code/aml-score.py` 文件中。

import os
import json
from inference_model import InferenceModel
 
from azureml.contrib.services.aml_request import rawhttp
from azureml.contrib.services.aml_response import AMLResponse
 
def init():
    global model
    model_path = os.path.join(
        os.getenv("AZUREML_MODEL_DIR"), "mnist-tf-model.h5" # model-path/model-file-name
    )
 
    model = InferenceModel(model_path)
 
@rawhttp
def run(request):
    if request.method != 'POST':
        return AMLResponse(f"Unsupported verb: {request.method}", 400)
 
    image_data = request.files['image']
    preds = model.predict(image_data)
    
    return AMLResponse(json.dumps({"preds": preds.tolist()}), 200)

除了模型的名称 (`mnist-tf-model.h5`),其余代码与前两篇文章相同。

当终结点启动时,代码会调用第一个方法 `init` 一次。这是一个加载模型的好地方。为此,我们使用 `AZUREML_MODEL_DIR` 环境变量,它指示模型文件的存储位置。

下面的 `run` 方法很简单。首先,我们确保它只接受 `POST` 请求。我们从请求中检索图像,然后 `run` 并 `return` 预测。

请注意,需要 `@rawhttp` 装饰器才能访问原始请求数据(在我们的例子中是二进制图像内容)。如果没有该装饰器,传递给 `run` 方法的 `request` 参数将仅限于解析后的 JSON。

配置在线终结点

除了代码,我们还需要三个额外的配置文件。

第一个文件,`endpoint-code/aml-env.yml`,存储了 Conda 环境定义。

channels:
  - conda-forge
  - defaults
dependencies:
  - python=3.7.10
  - pillow=8.3.2
  - gunicorn=20.1.0
  - numpy=1.19.5
  - pip
  - pip:
    - h5py==3.1.0 # We need to use the same version we have used for training
    - keras==2.6.0 # We need to add it manually, 
    - because a different (newer) version could be installed with the tensorflow otherwise!
    - tensorflow==2.6.0
    - azureml-defaults==1.35.0 
    - inference-schema[numpy-support]==1.3.0

请注意,要使用 Keras 从 H5 格式加载经过训练的模型,我们需要使用与模型训练期间使用的相同版本的 h5py、Keras 和 TensorFlow 库。这就是为什么我们将所有三个显式添加到 YAML 文件中。否则,其他版本将作为 TensorFlow 依赖项安装。

其余的配置文件定义了终结点和终结点的部署,并且等同于我们在前两篇文章中介绍的文件。

文件 `aml-endpoint.yml`

$schema: https://azuremlschemas.azureedge.net/latest/managedOnlineEndpoint.schema.json
name: mnisttfoep
auth_mode: key

File aml-endpoint-deployment.yml:

$schema: https://azuremlschemas.azureedge.net/latest/managedOnlineDeployment.schema.json
name: blue
endpoint_name: mnisttfoep
model: azureml:mnist-tf-model:1
code_configuration:
  code: 
    local_path: ./endpoint-code
  scoring_script: aml-score.py
environment: 
  conda_file: ./endpoint-code/aml-env.yml
  image: mcr.microsoft.com/azureml/minimal-ubuntu18.04-py37-cpu-inference:latest
instance_type: Standard_F2s_v2
instance_count: 1

部署在线终结点

准备好所有这些文件后,我们就可以开始部署了。为此,我们使用与前面文章中使用的相同的脚本,从终结点开始。

$ ENDPOINT_NAME="<your-endpoint-name>"

$ az ml online-endpoint create -n $ENDPOINT_NAME -f aml-endpoint.yml 
  --subscription $AZURE_SUBSCRIPTION --resource-group $RESOURCE_GROUP 
  --workspace-name $AML_WORKSPACE

请记住,终结点名称将成为 URL 的一部分,因此它必须是全局唯一的。

接下来,我们可以部署推理代码

$ az ml online-deployment create -n blue 
  --endpoint $ENDPOINT_NAME -f aml-endpoint-deployment.yml 
  --all-traffic --subscription $AZURE_SUBSCRIPTION 
  --resource-group $RESOURCE_GROUP --workspace-name $AML_WORKSPACE

经过很长时间,命令应该返回部署完成且终结点已准备好使用的确认。请注意,预配 VM、下载基本映像和设置环境需要一段时间。

然后,输入以下命令检查终结点日志

$ az ml online-deployment get-logs -n blue --endpoint $ENDPOINT_NAME 
  --subscription $AZURE_SUBSCRIPTION --resource-group $RESOURCE_GROUP 
  --workspace-name $AML_WORKSPACE

测试在线终结点

我们使用示例代码中的图像(`test-images/d0.png` 到 `d9.png`)来测试我们的服务。

此外,我们还需要终结点 URL 和终结点密钥。获取它们的一种方法是通过 Azure 机器学习工作室,从终结点的**使用**选项卡中获取。

此外,我们还可以使用 Azure CLI 获取这些值

$ SCORING_URI=$(az ml online-endpoint show -n $ENDPOINT_NAME -o tsv 
   --query scoring_uri --resource-group $RESOURCE_GROUP --workspace $AML_WORKSPACE)

$ ENDPOINT_KEY=$(az ml online-endpoint get-credentials --name $ENDPOINT_NAME 
  --subscription $AZURE_SUBSCRIPTION --resource-group $RESOURCE_GROUP 
  --workspace-name $AML_WORKSPACE -o tsv --query primaryKey)

填充 `SCORING_URI` 和 `ENDPOINT_KEY` 变量后,我们可以调用我们的服务

$ curl -X POST -F 'image=@./test-images/d8.png' -H 
  "Authorization: Bearer $ENDPOINT_KEY" $SCORING_URI

如果一切顺利,我们应该得到预期的答案

{"preds": [8]}

扩展托管终结点

使用云而不是本地基础设施的好处之一是扩展的灵活性。新的 Azure 机器学习托管终结点实现了这种灵活性和扩展。

当我们转到 Azure 门户中的部署资源时,我们可以手动扩展它或配置自动扩展。我们手动设置集群大小,如下面的屏幕截图所示:**扩展 > 配置**

我们为自动扩展设置扩展规则。例如,当**CPU 内存利用率**指标在 10 分钟内超过 70% 时,我们可以将实例数量增加一个,如下面的屏幕截图所示。

使用蓝绿部署

最后,我们来谈谈为什么我们使用“蓝色”作为部署名称。这个名称来自蓝绿部署,我们部署系统的新版本(例如“绿色”),而旧版本(“蓝色”)仍在运行并可供用户使用。绿色部署完成后,我们可以将所有流量从旧的“蓝色”版本切换到新的“绿色”版本。如果出现任何问题,我们可以立即反转此流量切换,而无需耗时的回滚操作。

所有这些更改都对服务调用者隐藏。调用者持续使用相同的 URL,可能不会意识到任何更改。

这种技术的一种演变称为金丝雀发布。主要区别在于,与蓝绿部署相反,在金丝雀发布中,我们不会一次将所有流量从一个版本切换到另一个版本。相反,我们逐步转移流量。我们可以使用类似的机制进行 A/B 测试,我们希望在少量请求(例如,百分之五)上测试新服务。

托管终结点原生支持所有这些方法。要添加新的部署,我们只需调用 `az ml online-deployment create` 命令,提供一个新名称,例如 `green`。

$ az ml online-deployment create -n green 
   --endpoint $ENDPOINT_NAME -f aml-endpoint-deployment.yml --all-traffic 
   --subscription $AZURE_SUBSCRIPTION --resource-group $RESOURCE_GROUP 
   --workspace-name $AML_WORKSPACE

请注意,只要我们使用 `--all-traffic` 属性,一旦操作完成,所有指向我们终结点的流量将自动重定向到新的 `green` 版本。实际上,这意味着蓝绿部署。

如果我们更喜欢金丝雀发布,我们可以在部署完成后设置流量分割。

这种方法的主要缺点是增加了 Azure 资源使用量。在每次发布期间和之后,已使用和尚未使用 VM 实例将影响 Azure vCPU 配额和产生的成本。只要您持续扩展部署以匹配当前的流量分配,您就可以通过金丝雀发布来限制这种影响。

从 Azure 函数访问在线终结点

在实际的生产系统(例如 Web 应用程序)中,您很可能不会直接将在线终结点暴露给公共客户端。通常,终结点作为微服务位于防火墙后面,并由其他服务使用。在本文的最后一部分,我们将创建一个简单的 Azure 函数应用程序来消费在线终结点并充当其公共代理。

我们将使用带有 Azure Functions 扩展的 Visual Studio Code

安装扩展后,我们需要选择**Azure**选项卡,然后单击**函数**部分中的**新建项目**图标。

接下来,我们选择项目的文件夹、编程语言、函数模板、第一个函数名称,最后是函数的授权级别。

我们选择 Python 语言、一个名为 `AmlMnistHttpTrigger` 的 HTTP 触发器模板和匿名授权级别。

很快,我们的函数将填充示例代码。

现在,我们所需要做的就是添加一个 `import` 并替换 `__init__.py` 文件的内容。

import requests
import azure.functions as func
 
def main(req: func.HttpRequest) -> func.HttpResponse:
    image_file = req.files['image']
    files = { "image": image_file }
 
    url = '<your-endpoint-uri>'
    api_key = '<your-api-key>' 
    headers = {'Authorization':('Bearer '+ api_key)}
 
    response = requests.post(url, files=files, headers=headers)
    return func.HttpResponse(response.content, status_code=response.status_code)</your-api-key></your-endpoint-uri>

这里主要方法的逻辑很简单。首先,它从请求中提取图像文件,然后创建并执行对我们的托管终结点 URL 的请求,同时传递图像和包含 `api_key` 值的标头。URL 和 `api_key` 都是我们之前与 curl 一起使用的相同值。

本示例经过简化,以便于理解。在实践中,您至少应该更改两件事。首先,您不应将 `api_key` 值直接包含在代码中。相反,请考虑使用 Key Vault 或类似机制来存储您的密钥。其次,您可能需要一些异常处理和日志记录。

为了使我们的函数正常工作,我们还需要通过添加 `requests` 库来更新函数应用程序文件夹中的 `requirements.txt` 文件。

azure-functions
requests

现在我们有了代码,我们可以通过选择**部署到函数应用**来发布我们的函数。

在为我们的 Azure 函数选择一个全局唯一的名称、运行时堆栈(例如 Python 3.9)和位置后,部署过程在后台开始。部署完成后,新触发器的 URL 将显示在输出日志的末尾。

我们还可以检查我们的函数并使用 Azure 门户获取其 URL。在**代码 + 测试**中,选择**获取函数 URL**。

现在,我们可以调用我们的函数

$ curl -X POST -F 'image=@test-images/d4.png' <your-function-url>

就是这样!由于我们的函数接受匿名调用者,我们无需在请求中传递任何授权令牌。

删除 Azure 资源

删除您不再需要的所有资源,以减少 Azure 费用。特别是,请记住托管终结点和 Azure 函数。您可能已在具有函数应用名称的专用资源组中创建了 Azure 函数。

后续步骤

在本文中,我们发布了一个经过训练以识别手写数字的 TensorFlow 模型。首先,我们使用了 Azure 机器学习在线终结点并解释了相关的基本扩展和部署选项。接下来,我们演示了如何从代码中调用此终结点,并以一个简单的 Azure 函数为例。

使用 Azure 部署 XGBoostPyTorch 或 TensorFlow 模型非常简单。此外,您还可以无限制地扩展,并获得运行模型所需的所有处理能力。

现在您知道了它有多容易,注册并使用 Azure 部署您自己的机器学习模型。无需运维团队。

要了解如何使用在线终结点(预览版)部署机器学习模型,请查看使用在线终结点部署和评分机器学习模型

© . All rights reserved.