将模型部署到 Azure Functions

了解如何通过 Azure Functions 无服务器环境部署预先训练的 ML.NET 机器学习模型,以便通过 HTTP 进行预测。

先决条件

Azure Functions 示例概述

此示例是 C# HTTP 触发器 Azure Functions 应用程序 ,它使用预先定型的二进制分类模型将文本的情绪分类为消极或积极。 Azure Functions 提供了一种简便的方法,可以在云中托管的无服务器环境中大规模运行少量代码。 若要获取此示例的代码,请参阅 GitHub 上的 dotnet/machinelearning-samples 存储库。

创建 Azure Functions 项目

  1. 在 Visual Studio 2022 中,打开“创建新项目”对话框。

  2. 在“创建新项目”对话框中,选择“Azure Functions”项目模板。

  3. 在“名称”文本框中,键入“SentimentAnalysisFunctionsApp”,然后选择“下一步”按钮。

  4. 在“其他信息”对话框中,将所有默认值保留原样,然后选择“创建”按钮。

  5. 安装“Microsoft.ML NuGet 包”

    1. 在“解决方案资源管理器”中,右键单击项目,然后选择“管理 NuGet 包” 。
    2. 选择“nuget.org”作为“包源”。
    3. 选择“浏览”选项卡。
    4. 搜索 Microsoft.ML。
    5. 在列表中选择该包,然后选择“安装”按钮。
    6. 选择“预览更改” 对话框中的“确定” 按钮
    7. 如果同意所列包的许可条款,请选择“接受许可” 对话框中的“我接受” 按钮。

    按照相同的步骤安装 Microsoft.Extensions.ML、Microsoft.Extensions.DependencyInjection 和 Microsoft.Azure.Functions.Extensions NuGet 包。

将预先训练的模型添加到项目

  1. 在项目中创建名为“MLModels”的目录,用于保存预生成的模型:在“解决方案资源管理器”中,右键单击项目,然后选择“添加”>“新文件夹”。 键入“MLModels”,再按 Enter。
  2. 将预生成的模型复制到 MLModels 文件夹。
  3. 在“解决方案资源管理器”中,右键单击预生成的模型文件,并选择“属性” 。 在“高级”下,将“复制到输出目录”的值更改为“如果较新则复制” 。

创建 Azure 函数用于分析情绪

创建情绪预测类。 向项目添加一个新类:

  1. 在“解决方案资源管理器”中,右键单击项目,然后选择“添加”>“新建 Azure 函数”

  2. 在“添加新项” 对话框中,选择“Azure 函数” ,并将“名称” 字段更改为“AnalyzeSentiment.cs” 。 然后,选择“添加” 按钮。

  3. 在“新建 Azure 函数”对话框中,选择“Http 触发器”,然后从“授权级别”下拉列表中选择“匿名”。 然后,选择“确定” 按钮。

    此时,AnalyzeSentiment.cs 文件在代码编辑器中打开。 将以下 using 语句添加到 AnalyzeSentiment.cs 的顶部:

    using System;
    using System.IO;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Azure.WebJobs;
    using Microsoft.Azure.WebJobs.Extensions.Http;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Logging;
    using Newtonsoft.Json;
    using Microsoft.Extensions.ML;
    using SentimentAnalysisFunctionsApp.DataModels;
    

    默认情况下,AnalyzeSentiment 类为 static。 确保从类定义中删除 static 关键字。

    public class AnalyzeSentiment
    {
    
    }
    

创建数据模型

需要为输入数据和预测创建一些类。 向项目添加一个新类:

  1. 在项目中创建“DataModels”目录,用于保存数据模型:在“解决方案资源管理器”中,右键单击项目,并依次选择“添加”>“新文件夹”。 键入“DataModels”,再按 Enter。

  2. 在“解决方案资源管理器”中,右键单击“DataModels”目录,然后选择“添加”>“类”。

  3. 在“添加新项” 对话框中,选择“类” 并将“名称” 字段更改为“SentimentData.cs” 。 然后,选择“添加”按钮。

    “SentimentData.cs” 文件随即在代码编辑器中打开。 将以下 using 语句添加到 SentimentData.cs 顶部:

    using Microsoft.ML.Data;
    

    删除现有类定义,并将以下代码添加到 SentimentData.cs 文件:

    public class SentimentData
    {
        [LoadColumn(0)]
        public string SentimentText;
    
        [LoadColumn(1)]
        [ColumnName("Label")]
        public bool Sentiment;
    }
    
  4. 在“解决方案资源管理器”中,右键单击“DataModels”目录,然后选择“添加”>“类”。

  5. 在“添加新项” 对话框中,选择“类” ,并将“名称” 字段更改为“SentimentPrediction.cs” 。 然后,选择“添加” 按钮。 此时,SentimentPrediction.cs 文件在代码编辑器中打开。 将以下 using 语句添加到 SentimentPrediction.cs 顶部:

    using Microsoft.ML.Data;
    

    删除现有类定义,并将以下代码添加到 SentimentPrediction.cs 文件:

    public class SentimentPrediction : SentimentData
    {
    
        [ColumnName("PredictedLabel")]
        public bool Prediction { get; set; }
    
        public float Probability { get; set; }
    
        public float Score { get; set; }
    }
    

    SentimentPrediction 继承自 SentimentData,后者提供对 SentimentText 属性中原始数据以及模型生成的输出的访问。

注册 PredictionEnginePool 服务

若要进行单一预测,必须创建 PredictionEnginePredictionEngine 不是线程安全。 此外,必须在应用程序中的每一处所需位置创建它的实例。 随着应用程序的增长,此过程可能会变得难以管理。 为了提高性能和线程安全,请结合使用依赖项注入和 PredictionEnginePool 服务,这将创建一个在整个应用程序中使用的 PredictionEngine 对象的 ObjectPool

若要详细了解依赖项注入,请单击下面的链接。

  1. 在“解决方案资源管理器”中,右键单击该项目,然后选择“添加”>“类”。

  2. 在“添加新项”对话框中,选择“类”并将“名称”字段更改为“Startup.cs” 。 然后,选择“添加” 按钮。

  3. 将以下 using 语句添加到 Startup.cs 的顶部:

    using Microsoft.Azure.Functions.Extensions.DependencyInjection;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.ML;
    using SentimentAnalysisFunctionsApp;
    using SentimentAnalysisFunctionsApp.DataModels;
    using System.IO;
    using System;
    
  4. 删除 using 语句下的现有代码,并添加以下代码:

    [assembly: FunctionsStartup(typeof(Startup))]
    namespace SentimentAnalysisFunctionsApp
    {
        public class Startup : FunctionsStartup
        {
    
        }
    }
    
  5. Startup 类中定义变量,以存储运行应用的环境以及模型所在的文件路径

    private readonly string _environment;
    private readonly string _modelPath;
    
  6. 在它下面创建一个构造函数,以设置 _environment_modelPath 变量的值。 应用程序在本地运行时,默认环境为“开发”。

    public Startup()
    {
        _environment = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT");
    
        if (_environment == "Development")
        {
            _modelPath = Path.Combine("MLModels", "sentiment_model.zip");
        }
        else
        {
            string deploymentPath = @"D:\home\site\wwwroot\";
            _modelPath = Path.Combine(deploymentPath, "MLModels", "sentiment_model.zip");
        }
    }
    
  7. 然后,在构造函数下面添加一个名为 Configure 的新方法,用于注册 PredictionEnginePool 服务。

    public override void Configure(IFunctionsHostBuilder builder)
    {
        builder.Services.AddPredictionEnginePool<SentimentData, SentimentPrediction>()
            .FromFile(modelName: "SentimentAnalysisModel", filePath: _modelPath, watchForChanges: true);
    }
    

概括地讲,此代码在应用程序请求时自动初始化对象和服务供以后使用,无需手动执行初始化。

机器学习模型不是静态的。 随着新的训练数据变得可用,模型将重新训练和重新部署。 将最新版本的模型引入应用程序的一种方法是重启或重新部署应用程序。 但这会导致应用程序关闭。 PredictionEnginePool 服务提供一种机制,用于在不重启或重新部署应用程序的情况下重新加载已更新的模型。

watchForChanges 参数设置为 true,则 PredictionEnginePool 会启动 FileSystemWatcher,用于侦听文件系统更改通知并在文件发生更改时引发事件。 这会提示 PredictionEnginePool 自动重新加载模型。

模型由 modelName 参数标识,因此更改时可以重新加载每个应用程序的多个模型。

提示

或者,如果使用远程存储的模型,则可以使用 FromUri 方法。 FromUri 会轮询远程位置以获取更改,而不是监视文件更改事件。 轮询间隔默认为 5 分钟。 你可以根据应用程序的要求,增加或减少轮询间隔。 在下面的代码示例中,PredictionEnginePool 每分钟轮询存储在指定 URI 中的模型。

builder.Services.AddPredictionEnginePool<SentimentData, SentimentPrediction>()
  .FromUri(
      modelName: "SentimentAnalysisModel",
      uri:"https://github.com/dotnet/samples/raw/main/machine-learning/models/sentimentanalysis/sentiment_model.zip",
      period: TimeSpan.FromMinutes(1));

将模型加载到函数中

AnalyzeSentiment 类中插入以下代码:

public AnalyzeSentiment(PredictionEnginePool<SentimentData, SentimentPrediction> predictionEnginePool)
{
    _predictionEnginePool = predictionEnginePool;
}

此代码通过将 PredictionEnginePool 传递给函数的构造函数(通过依赖项注入获得)来分配它。

使用模型进行预测

将 AnalyzeSentiment 类中 Run 方法的现有实现替换为以下代码:

public async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
    ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");

    // Parse HTTP Request Body
    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    SentimentData data = JsonConvert.DeserializeObject<SentimentData>(requestBody);

    //Make Prediction
    SentimentPrediction prediction = _predictionEnginePool.Predict(modelName: "SentimentAnalysisModel", example: data);

    //Convert prediction to string
    string sentiment = Convert.ToBoolean(prediction.Prediction) ? "Positive" : "Negative";

    //Return Prediction
    return new OkObjectResult(sentiment);
}

执行 Run 方法时,来自 HTTP 请求的传入数据被反序列化并用作 PredictionEnginePool 的输入。 然后,调用 Predict 方法,使用在 Startup 类中注册的 SentimentAnalysisModel 进行预测,并在成功时将结果返回给用户。

在本地测试

至此,已完成所有设置,是时候测试应用程序了:

  1. 运行此应用程序

  2. 打开 PowerShell,并在提示符中输入代码(其中,PORT 是应用程序运行所在的端口)。 端口通常是 7071。

    Invoke-RestMethod "http://localhost:<PORT>/api/AnalyzeSentiment" -Method Post -Body (@{SentimentText="This is a very bad steak"} | ConvertTo-Json) -ContentType "application/json"
    

    如果成功,输出文本应如下所示:

    Negative
    

祝贺你! 你已成功使用 Azure Functions 通过 Internet 提供模型进行预测。

后续步骤