目录
入门
注册模型
创建模型推理代码
构建FastAPI服务代码
使用Python和FastAPI发布应用服务
更新FastAPI的应用服务配置
授予应用服务应用程序权限
测试服务
检查应用服务日志
使用Azure机器学习和托管终结点
创建在线端点代码
配置在线端点
创建托管在线端点
测试在线端点
本地部署
删除Azure资源
下一步
这是3部分系列文章的第2部分,演示如何获取使用各种Python AI框架构建的AI模型,并使用Azure ML托管终结点部署和扩展它们。在这篇文章中,PyTorch模型被训练来识别手写数字。我们将FastAPI与应用服务一起使用,然后使用Azure机器学习在线端点。
机器学习(ML)通常需要大量的处理能力。尽管您的Python ML项目可能超出您当前计算机的能力,但您可以使用Azure运行几乎任何规模的ML工作负载。
在这个由三部分组成的系列的第一篇文章中,我们发布了一个XGBoost模型,该模型经过训练可以从著名的MNIST数据集中识别手写数字。我们将Azure App Service与Flask一起使用,然后使用机器学习在线端点。在本教程中,我们将使用上一个系列中的另一个模型。该模型要求更高,但允许我们探索更高级的场景。
我们的目标是为机器学习模型的实时推理创建自定义Rest API服务。我们将首先使用Python、FastAPI和Azure应用服务发布PyTorch模型。然后,我们将使用相同的模型创建一个在线端点,这是一个相对较新的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命令行工具:
$ az extension add -n ml -y
最后但同样重要的是,如果您还没有Azure帐户,请注册一个免费的Azure帐户,并享受数百美元的积分和访问各种服务。
准备好所有这些资源后,登录您的订阅。
$ az login
然后,设置以下环境变量以在脚本中使用:
export AZURE_SUBSCRIPTION=""
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机器学习工作区的内置模型注册表。
在上一个系列的第二篇文章中,我们已经将模型保存到了注册表中。如果您还没有它,只需运行以下命令:
$ az ml model create --name "mnist-pt-model" --local-path "./mnist.pt_model"
--subscription $AZURE_SUBSCRIPTION --resource-group $RESOURCE_GROUP
--workspace-name $AML_WORKSPACE
与上一篇文章一样,我们的Rest API服务的核心部分是加载模型并对图像数据运行推理的代码。我们将代码存储在inference_model.py文件中。
文件以import开头:
import numpy as np
from PIL import Image
import torch
from torch.nn import functional as F
from torch import nn
接下来,我们的模型需要一个class。它必须与我们用于训练的代码相同。
class NetMNIST(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
self.fc1 = nn.Linear(320, 50)
self.fc2 = nn.Linear(50, 10)
def forward(self, x):
x = F.max_pool2d(F.relu(self.conv1(x)), (2,2))
x = F.max_pool2d(F.dropout(F.relu(self.conv2(x)), p=0.2), (2,2))
x = x.view(-1, 320)
x = F.relu(self.fc1(x))
x = F.dropout(x, p=0.2, training=self.training)
x = self.fc2(x)
return F.log_softmax(x, dim=1)
现在,我们可以添加我们的InferenceModel类:
class InferenceModel():
def __init__(self, model_path):
is_cuda_available = torch.cuda.is_available()
self.device = torch.device("cuda" if is_cuda_available else "cpu")
self.model = NetMNIST().to(self.device)
self.model.load_state_dict(torch.load(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 torch.tensor(image_np).float().to(self.device)
def predict(self, image_bytes):
image_data = self._preprocess_image(image_bytes)
with torch.no_grad():
prediction = self.model(image_data.reshape(-1,1,28,28)).cpu().numpy()
return np.argmax(prediction, axis=1)
与我们之前的XGBoost模型一样,该文件包含一个类,InferenceModel。该类具有三个方法:__init__、_preprocess_image和predict。
该__init__方法从文件加载模型并将其存储以供以后使用。它还会检测是否有可用的CUDA GPU。
该_preprocess_image方法调整图像大小并将其转换为模型可接受的格式。对于我们的PyTorch模型,它是一个单通道28x28张量,其浮点数范围为0.0到1.0。注意像素强度的反转。我们这样做是因为我们计划在标准的黑白图像上使用我们的模型,而MNIST训练数据集具有反转的黑白值。
最后一种predict方法使用检测到的设备和加载的模型对提供的图像数据进行推理。
与之前系列中的训练代码相反,我们不使用数据加载器。我们将图像数据直接提供给模型。数据加载器允许处理成批的数据。我们的API一次处理一个图像,使数据加载器变得多余。
构建FastAPI服务代码现在我们有了处理预测的代码,我们可以在我们的Rest API服务中使用它。让我们创建一个自定义Flask服务来执行这项工作。
新的main.py文件将包含所有服务代码,文件结构将镜像上一篇文章中的app.py文件:
from fastapi import FastAPI, File
from io import BytesIO
from inference_model import InferenceModel
from azureml.core.authentication import MsiAuthentication
from azureml.core import Workspace
from azureml.core.model import Model
def get_inference_model():
global model
if model == None:
auth = MsiAuthentication()
ws = Workspace(subscription_id="",
resource_group="azureml-rg",
workspace_name="demo-ws",
auth=auth)
aml_model = Model(ws, 'mnist-pt-model', version=1)
model_path = aml_model.download(target_dir='.', exist_ok=True)
model = InferenceModel(model_path)
return model
app = FastAPI(title="PyTorch MNIST Service API", version="1.0")
@app.post("/score")
async def score(image: bytes = File(...)):
if not image:
return {"message": "No image_file"}
model = get_inference_model()
preds = model.predict(BytesIO(image))
return {"preds": str(preds)}
model = None
和以前一样,我们使用MsiAuthentication类进行身份验证,以从我们的Azure机器学习工作区访问资源。该类MsiAuthentication依赖于Azure Active Directory中的托管标识。我们将托管标识分配给Azure资源,例如虚拟机或应用服务。使用托管身份使我们免于维护任何凭据或机密。
与上一篇文章相比,该get_inference_model方法的唯一变化是模型名称。
最后一个方法 ,score负责运行预测。与FastAPI中的每个API方法一样,它需要是异步的,因此是async关键字。请注意,score方法声明中的File(…)语句不是占位符。
使用Python和FastAPI发布应用服务我们几乎拥有使用Python和FastAPI的App Service在Azure上运行REST API服务的所有代码。
我们需要的最后一个文件是requirements.txt,内容如下:
fastapi
gunicorn
uvicorn
python-multipart==0.0.5
torch==1.9.0
pillow==8.3.2
azureml-defaults==1.35.0
此代码没有为 fastapi、gunicorn和uvicorn依赖项定义显式版本。这是故意的,我们会在本节结束之前证明它的合理性。
现在,我们可以使用Azure CLI使用以下命令(从包含我们文件的文件夹)发布我们的应用服务应用程序:
$ APP_SERVICE_PLAN=""
$ APP_NAME=""
$ az webapp up --name $APP_NAME --plan $APP_SERVICE_PLAN --sku B2 --os-type Linux
--runtime "python|3.7" --subscription $AZURE_SUBSCRIPTION --resource-group $RESOURCE_GROUP
请记住,该APP_NAME值必须是全局唯一的,因为它将是服务URL的一部分。
不过,发布应用程序可能需要更长的时间。在一些不那么罕见的情况下,它可能根本不会结束。这可能与已安装依赖项的大小有关。仅PyTorch就占用了1 GB,迫使服务计划SKU从免费的F1层(具有1 GB的RAM)更改为至少B2(具有3.5GB 的RAM)。即便如此,部署过程中也发生了随机超时,这导致了看似永无止境或失败的部署过程。
强制执行特定版本的依赖项会进一步增加尝试失败的可能性。它可能与默认应用服务映像中的其他包冲突有关。一些库的requirements.txt文件中的显式版本已被删除以减少这些问题。
所有这些都表明,使用应用服务部署为复杂模型提供服务可能并不总是生产应用程序的最佳选择。幸运的是,Azure为我们提供了许多替代方案。
一种是Web App for Containers。我们可以使用自己的Docker容器完全控制所有依赖项,从而限制潜在问题。我们还可以使用Container Instances或AKS。
不过,有一个新选项:托管在线端点。我们将在本文后面使用它。
现在,让我们回到我们的应用服务部署。当我们的命令完成时,它应该返回类似于以下内容的JSON:
{
"URL": "http://.azurewebsites.net",
"appserviceplan": "",
"location": "westeurope",
"name": "",
"os": "Linux",
"resourcegroup": "azureml-rg",
"runtime_version": "python|3.7",
"runtime_version_detected": "-",
"sku": "B2",
"src_path": ""
}
应用服务使用默认的Gunicorn工作者自动检测和处理Flask应用程序。但是,这次我们使用FastAPI。
我们需要使用以下Azure CLI命令显式设置应用程序的启动命令:
$ az webapp config set --name $APP_NAME --startup-file "gunicorn --workers=2
--worker-class uvicorn.workers.UvicornWorker main:app" --resource-group $RESOURCE_GROUP
此命令确保将Gunicorn与Uvicorn工作人员一起使用,这是运行我们的FastAPI服务所必需的。
或者,我们可以使用Azure门户并将启动命令粘贴到General settings的Startup Command字段中:
我们授予权限的方式与XGBoost模型相同。我们的服务必须从Azure机器学习工作区下载经过训练的模型,这需要授权。我们使用托管身份来避免凭据管理。
为我们的应用程序分配一个新的托管标识很简单。输入以下内容:
$ az webapp identity assign --name $APP_NAME --resource-group $RESOURCE_GROUP
完成后,我们应该期待一个JSON输出:
{
"principalId": "",
"tenantId": "",
"type": "SystemAssigned",
"userAssignedIdentities": null
}
配备返回值,我们可以添加所需的权限:
$ az role assignment create --role reader --assignee
--scope /subscriptions/$AZURE_SUBSCRIPTION/resourceGroups/$RESOURCE_GROUP/providers/
Microsoft.MachineLearningServices/workspaces/$AML_WORKSPACE
读者角色应该足以满足我们的目的。如果我们的应用程序需要创建额外的资源,我们应该使用贡献者角色。
测试服务我们使用示例代码中的图像(test-images/d0.png到d9.png)来测试我们的服务:
这些图像没有严格的要求。我们服务的代码将重新缩放图像并将其转换为预期的大小和格式。
我们需要发送POST请求来调用我们的服务。我们可以使用Postman或curl。使用curl,我们可以直接从命令行执行我们的请求。
$ curl -X POST -F 'image=@./test-images/d6.png' https://$APP_NAME.azurewebsites.net/score
如果一切顺利,我们应该期待以下响应:
{"preds": [6]}
答案似乎是正确的。我们的PyTorch模型应该比上一篇文章中的简单XGBoost模型工作得更好。
检查应用服务日志如果您遇到任何问题,您可能需要检查您的服务日志。您可以通过Azure 门户 监控 > 日志流选项卡执行此操作。
或者,您可以使用Azure CLI访问日志。
$ az webapp log tail --name $APP_NAME --subscription $AZURE_SUBSCRIPTION
--resource-group $RESOURCE_GROUP
请注意,只会出现执行日志。如果部署存在问题,请尝试Azure门户的部署中心日志。
使用托管为Azure应用服务的自定义Flask应用程序发布我们的模型非常简单。但是,我们的环境越复杂,我们在设置过程中遇到的问题就越多。我们可以使用Web App for Containers或托管在线端点(预览版)来避免这些设置问题。在以下部分中,我们将使用端点。
创建在线端点代码与上一篇文章一样,我们将在inference_model.py文件中使用我们为Flask应用程序创建的相同InferenceModel类,并将其复制到新文件夹endpoint-code中。
因为InferenceModel类完全抽象了模型,所以我们在文件endpoint-code/aml-score.py中的端点代码可以与XGBoost模型几乎完全相同:
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.pt_model"
)
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.pt_model。
其余的保持不变。代码在端点启动时调用第一个init方法。这是加载我们模型的好地方。我们使用AZUREML_MODEL_DIR环境变量来执行此操作,它指示我们的模型文件在哪里。
下面的run方法很简单。首先,我们确保只接受POST请求。接下来,我们从请求中检索图像,然后运行并返回预测。
注意@rawhttp装饰器。它访问原始请求数据,例如二进制图像内容。没有它,传递给run方法的请求参数将仅限于解析的JSON。
配置在线端点除了代码,我们还需要三个配置文件。
第一个,endpoint-code/aml-env.yml,存储Conda环境定义。
channels:
- pytorch
- conda-forge
- defaults
dependencies:
- python=3.7.10
- pytorch=1.9.0
- cpuonly # Replace with cudatoolkit to use GPU
- pillow=8.3.2
- gunicorn=20.1.0
- numpy=1.19.5
- pip:
- azureml-defaults==1.35.0
- inference-schema[numpy-support]==1.3.0
以下两个文件看起来与上一篇文章中的相同。它们包含端点及其部署的配置。
端点配置文件aml-endpoint.yml包含:
$schema: https://azuremlschemas.azureedge.net/latest/managedOnlineEndpoint.schema.json
name: mnistptoep
auth_mode: key
最后一个文件aml-endpoint-deployment.yml包含:
$schema: https://azuremlschemas.azureedge.net/latest/managedOnlineDeployment.schema.json
name: blue
endpoint_name: mnistptoep
model: azureml:mnist-pt-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
您可以使用自定义图像或Microsoft精选图像目录中的图像。可用图像的列表非常广泛,尽管在我们的案例中没有理由使用其中的大部分。无论您选择哪个图像,代码仍将根据您的规范创建一个新的Conda环境。
我们需要在我们的环境中包含通用组件,即使它们已经存在于图像中。例如,我们可以使用azureml-inference-server-http(从azureml-defaults中)和gunicorn。
尽管如此,如果您需要一些未包含在您的Conda环境中的系统级依赖项(例如Open MPI或CUDA驱动程序),您很可能会在那里找到您需要的东西。在我们的例子中,选择的最小图像就足够了。
请注意,虽然Microsoft建议始终使用最新的图像标签,但在某些情况下,您可能会考虑使用固定值以获得最大的再现性。您可以使用以下URL模板找到给定图像的所有可用标签:
https://mcr.microsoft.com/v2///tags/list
例如,这个网址:
https://mcr.microsoft.com/v2/azureml/minimal-ubuntu18.04-py37-cpu-inference/tags/list
返回:
截图显示每个月有多个版本,所以图片更新频繁。
创建托管在线端点准备好所有这些文件后,我们就可以开始部署了。我们从端点开始:
$ 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
与之前的应用服务应用程序名称一样,每个Azure区域的端点名称必须是唯一的,因为它将成为URL的一部分,格式如下:
https://..inference.ml.azure.com/score
现在我们已经创建了端点,我们终于可以部署我们的推理代码了:
$ 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
很长一段时间后,该命令应返回确认部署已完成且端点已准备好使用。预配虚拟机、下载基础映像和设置环境需要时间。
要检查端点日志,请键入:
$ az ml online-deployment get-logs -n blue --endpoint $ENDPOINT_NAME
--subscription $AZURE_SUBSCRIPTION --resource-group $RESOURCE_GROUP
--workspace-name $AML_WORKSPACE
输出应类似于以下屏幕截图:
我们需要三个东西来调用我们的端点:端点URL、端点键和示例图像。我们已经使用了示例图像,因此我们只需要URL和密钥。
获取它们的一种方法是通过Azure Machine Learning Studio,从端点的使用选项卡。
我们还可以使用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/d5.png' -H
"Authorization: Bearer $ENDPOINT_KEY" $SCORING_URI
如果一切顺利,我们应该得到与Flask应用程序相同的答案:
{"preds": [5]}
托管端点的一个简洁功能是可以选择使用本地Docker实例将它们部署在您的计算机上。它可以在调试部署问题时提供帮助,因为它使您可以完全访问Docker日志和创建的容器。
只需在az ml online-endpoint命令中添加--local参数即可使用此功能。例如,以下命令将创建端点的本地版本:
$ az ml online-endpoint create --local -n $ENDPOINT_NAME -f aml-endpoint.yml
--subscription $AZURE_SUBSCRIPTION --resource-group $RESOURCE_GROUP
--workspace-name $AML_WORKSPACE
$ az ml online-deployment create --local -n blue
--endpoint $ENDPOINT_NAME -f aml-endpoint-deployment.yml
--all-traffic --subscription $AZURE_SUBSCRIPTION
--resource-group $RESOURCE_GROUP --workspace-name $AML_WORKSPACE
大多数(如果不是全部)az ml online-endpoint / online-deployment子命令都支持该--local标志。例如,您可以通过以下方式调用本地端点URI并检查其日志:
$ LOCAL_SCORING_URI=$(az ml online-endpoint show --local -n $ENDPOINT_NAME -o tsv
--query scoring_uri --resource-group $RESOURCE_GROUP --workspace $AML_WORKSPACE)
$ curl -X POST -F 'image=@./test-images/d5.png' $LOCAL_SCORING_URI
$ az ml online-deployment get-logs --local -n blue --endpoint $ENDPOINT_NAME
--subscription $AZURE_SUBSCRIPTION --resource-group $RESOURCE_GROUP
--workspace-name $AML_WORKSPACE
我们这里不需要API_KEY,因为本地部署不需要身份验证。
不过,还有更多。本地部署完成后,您就可以完全访问相应的Docker镜像和容器。例如:
$ docker images
$ docker ps
默认情况下,镜像名称与$ENDPOINT_NAME值匹配,标签等于部署名称(在我们的示例中为蓝色)。
您可以为镜像创建和运行自己的容器,或者像往常一样将镜像附加到现有容器。例如,您可以将镜像附加到正在运行的容器并在其中列出可用的Conda环境:
$ docker exec -it bash
amlenv是基础镜像中包含的默认Azure机器学习环境。我们使用YAML配置创建inf-conda-env环境。
当端点启动时,它不会明确选择活动环境。它将每个环境的二进制路径添加到PATH环境变量中。它最初添加inf-conda-env, 所以它首先考虑来自您的自定义环境的二进制文件。
当端点启动时,它会执行azureml-inference-server-http命令。如果自定义inf-conda-env环境中不存在,则运行默认版本amlenv。通过这种方式,它隐式地确定了执行的Conda环境。
删除Azure资源你可以删除不再需要的所有资源以减少Azure费用。尤其要记住应用服务计划和托管终结点。
下一步我们在本文中发布了一个经过训练可识别手写数字的PyTorch模型。我们将FastAPI与应用服务一起使用,然后使用Azure机器学习在线端点。
在本系列的第三篇也是最后一篇文章中,我们将使用在线端点发布TensorFlow模型。然后,我们将创建一个Azure函数作为此终结点的公共代理。此外,我们将探索托管端点的配置选项,例如自动缩放和蓝绿部署概念。
继续下一篇文章发布一个TensorFlow模型。
要了解如何使用在线端点(预览版)部署机器学习模型,请查看使用在线端点部署机器学习模型并对其进行评分。
https://www.codeproject.com/Articles/5328264/Deploying-Models-at-Scale-on-Azure-Part-2-Deployin