内容简介:通过上文所述案例,我们已经选择了最优回归算法来预测学生的综合成绩,并且完成了基于训练数据集的预测模型训练。从实现上,训练好的模型被保存成一个ZIP文件,以便在其它项目中直接调用以完成机器学习的实践场景。在本文中,我将介绍如何在ASP.NET Core中使用这个ZIP文件,以提供用于学生成绩预测的RESTful API。我们已经得到了经过ML.NET训练好的模型数据文件,也就是一个ZIP文件,在开发的RESTful API中,需要读入这个文件以便实现预测功能。于是,ZIP文件保存在何处就成为了我们首要解决的问
通过上文所述案例,我们已经选择了最优回归算法来预测学生的综合成绩,并且完成了基于训练数据集的预测模型训练。从实现上,训练好的模型被保存成一个ZIP文件,以便在其它项目中直接调用以完成机器学习的实践场景。在本文中,我将介绍如何在ASP.NET Core中使用这个ZIP文件,以提供用于学生成绩预测的RESTful API。
将模型文件保存到Azure Blob Storage中
我们已经得到了经过ML.NET训练好的模型数据文件,也就是一个ZIP文件,在开发的RESTful API中,需要读入这个文件以便实现预测功能。于是,ZIP文件保存在何处就成为了我们首要解决的问题。在开发环境,我们可以将ZIP文件保存在ASP.NET Core的运行目录中,可是,开发好的RESTful API最终还是要部署到生产环境,这种部署有可能是单节点的,也有可能是位于负载均衡服务器后端的多节点部署,而且模型文件也会随着训练数据集的增加或变化进行增量式更新,因此,依赖于部署环境的本地文件系统并不是一个好的做法。因此,我选择将模型文件保存在 Azure Blob Storage 中。
注意:为了防止在开发调试阶段过多使用Azure Blob Storage的流量,我们可以在ASP.NET Core的应用程序中实现两套模型数据供应器:一套从本地文件系统读入模型,用于开发环境,另一套从Azure Blob Storage读入模型,用于生产环境,然后通过ASP.NET Core的Hosting Environment进行区分以选择不同的供应器。
我们首先登录Microsoft Azure的主页,在主页中创建一个新的Storage Account。注意:我这里使用的是Global的Azure,对于由世纪互联运营的Azure,操作过程有可能不一样。
创建过程就不一一赘述了,根据自己的需要和钱包的厚度来决定所需的配置,待创建完成后,进入Storage Account的Access keys页面,注意其中的Connection string部分的值,接下来构建RESTful API的时候,需要用到这些值。值得一提的是,Azure会同时给你提供两个不同的Key和Connection String,因为经常更换Access key将会是一个良好的习惯,为了防止Access key更新时,应用程序无法正常工作,因此会有一个备用Key来保证程序的正常运行。我们先不管Azure Key Vault的事情,目前先把其中的某个Key复制下来。
然后,进入Blobs服务,新建一个容器(Container),比如命名为mlnetmodel,这个名字也要记下来。之后,在容器中上传我们的模型文件即可,如下:
在准备好模型文件之后,我们就可以开始开发RESTful API了。
使用ASP.NET Core开发RESTful API
打开宇宙第一最强IDE Visual Studio,我用的是2019的版本,新建一个ASP.NET Core的应用程序,启用 docker 支持,因为我们接下来会将这个应用程序编译成docker镜像,以便在容器中运行。详细的项目创建过程以及RESTful API实现过程我也就不多说明了,网上相关资料实在太多了。这里只强调几个需要重点注意的地方。
首先需要添加如下NuGet包的引用,由于我们需要使用ML.NET,并且需要访问Azure Blob Storage,因此,以下依赖项不可缺少:
- Microsoft.ML
- Microsoft.Azure.Storage.Blob
有点小坑的地方是,当你直接引用Microsoft.Azure.Storage.Blob时,编译项目会出错,提示所依赖的Microsoft.Azure.KeyVault.Core不支持.NET Standard。解决办法就是手工添加Microsoft.Azure.KeyVault.Core的依赖,我使用的是3.0.3的版本。
接下来,通过ASP.NET Core的配置系统,从配置数据中读入访问Azure Blob Storage所需的连接字符串参数,然后初始化Storage Account以及Blob Client对象,以便将保存在Azure Blob Storage中的模型文件下载下来。代码如下:
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); var defaultEndpointsProtocol = Configuration[BlobProtocolConfigName]; var accountName = Configuration[BlobAccountNameConfigName]; var accountKey = Configuration[BlobAccountKeyConfigName]; var endpointSuffix = Configuration[BlobEndpointSuffixConfigName]; var connectionString = $@"DefaultEndpointsProtocol={defaultEndpointsProtocol}; AccountName={accountName}; AccountKey={accountKey}; EndpointSuffix={endpointSuffix}"; var storageAccount = CloudStorageAccount.Parse(connectionString); var blobClient = storageAccount.CreateCloudBlobClient(); var mlnetContainer = blobClient.GetContainerReference("mlnetmodel"); var blob = mlnetContainer.GetBlobReference("student_perf_model.zip"); using (var ms = new MemoryStream()) { blob.DownloadToStream(ms); } // 其它代码暂时忽略 }
上面高亮的代码,通过blob对象,将模型文件下载到MemoryStream中。问题来了,干嘛不保存在本地文件中呢?因为我们接下来需要使用的ML.NET中的PredictionEngine(预测引擎)不是线程安全的,我们只能通过services.AddScoped方法来注册PredictionEngine的实例,也就是说,每当有一个新的HTTP请求到来时,PredictionEngine实例都需要构建一次,而PredictionEngine的构建是需要访问模型文件的,频繁的访问文件系统中的文件会损耗应用程序的性能。
因此,我构建了下面的数据结构,用来保存下载的模型数据:
public class ModelData { public ModelData(byte[] dataBytes) { this.DataBytes = dataBytes; } public byte[] DataBytes { get; } }
于是,上面的blob.DownloadToStream这部分代码,就可以改写为:
using (var ms = new MemoryStream()) { blob.DownloadToStream(ms); services.AddSingleton(new ModelData(ms.ToArray())); }
然后,通过如下方法来注册PredictionEngine实例:
services.AddScoped(serviceProvider => { // 通过serviceProvider获取已注册的MLContext对象 var mlContext = serviceProvider.GetRequiredService<MLContext>(); var dataStream = serviceProvider.GetRequiredService<ModelData>().DataBytes; using (var modelStream = new MemoryStream(dataStream)) { var model = mlContext.Model.Load(modelStream); return model.CreatePredictionEngine<StudentTrainingModel, StudentPredictionModel>(mlContext); } });
现在,我们已经完成了模型文件的下载,以及PredictionEngine实例的注册,接下来就非常简单了,只需要在API Controller中,使用构造器注入的PredictionEngine实例来实现我们的预测功能即可。代码非常简单:
[Route("api/[controller]")] [ApiController] public class StudentsController : ControllerBase { private readonly PredictionEngine<StudentTrainingModel, StudentPredictionModel> predictionEngine; public StudentsController(PredictionEngine<StudentTrainingModel, StudentPredictionModel> predictionEngine) { this.predictionEngine = predictionEngine; } [HttpPost("predict")] public IActionResult Predict([FromBody] StudentTrainingModel model) => Ok(predictionEngine.Predict(model)); }
至此,API编写完成,将API运行起来,并进行简单的测试:
测试成功。cURL命令从本地文件data.json中读入学生问卷调查数据,并预测他的综合成绩是12.8184786分(实际是9分,还是有点偏差)。
在容器中运行ASP.NET Core RESTful API应用程序
由于在创建ASP.NET Core应用程序时,已经选择了docker支持,因此,我们可以直接使用docker build命令来编译镜像,并使用docker run来运行容器。当然,在Windows环境下需要安装Docker for Windows,不过这里就不多说明安装步骤了,在我以前的博客中有详细介绍。为了方便编译和运行容器,我在ASP.NET Core的 上层目录 中建了一个docker-compose.yml文件,以使用docker compose来实现容器镜像的编译与容器的运行。在这里我强调“上层目录”,因为,docker-compose.yml文件中,已经通过相对路径指定了docker build的context路径。docker-compose.yml文件内容如下:
version: '3' services: mlnet_webapi: image: daxnet/mlnet_webapi build: context: . dockerfile: mlnet_webapi/Dockerfile environment: - BLOB_ACCOUNT_NAME=${BLOB_ACCOUNT_NAME} - BLOB_DEFAULT_ENDPOINTS_PROTOCOL=${BLOB_DEFAULT_ENDPOINTS_PROTOCOL} - BLOB_ENDPOINT_SUFFIX=${BLOB_ENDPOINT_SUFFIX} - BLOB_ACCOUNT_KEY=${BLOB_ACCOUNT_KEY} - Serilog__MinimumLevel=${Serilog__MinimumLevel:-Debug} container_name: mlnet_webapi ports: - 880:80 - 8443:443
值得一提的是,文件中环境变量都是通过.env文件注入进来的,因此,访问Azure Blob Storage的Connection String相关信息不会签入到Github代码库中。
使用docker-compose up命令一键编译并启动容器,再次访问我们的API以确保程序能够正常工作:
总结
本文主要介绍了如何在ASP.NET Core项目中使用ML.NET产生的训练模型,并向外界提供RESTful API,案例使用了容器技术,使得所生成的RESTful API应用能够在容器中运行,以便为下一步的持续部署做铺垫。在下文中,我将介绍基于Azure DevOps的持续集成与持续部署。
(总访问量:1;当日访问量:1)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 从Web端ssh连接到机器或者机器内的容器
- 解决因为机器性能问题导致docker-compose运行容器超时的问题
- ML.NET机器学习、API容器化与Azure DevOps实践(二):案例
- ML.NET机器学习、API容器化与Azure DevOps实践(一):简介
- ML.NET机器学习、API容器化与Azure DevOps实践(四):持续集成与k8s持续部署
- 容器技术之容器镜像篇
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
你不知道的JavaScript(中卷)
[美] Kyle Simpson / 单业、姜南 / 人民邮电出版社 / 2016-8 / 79.00元
JavaScript这门语言简单易用,很容易上手,但其语言机制复杂微妙,即使是经验丰富的JavaScript开发人员,如果没有认真学习的话也无法真正理解。本套书直面当前JavaScript开发人员不求甚解的大趋势,深入理解语言内部的机制,全面介绍了JavaScript中常被人误解和忽视的重要知识点。本书是其中卷,主要介绍了类型、语法、异步和性能。一起来看看 《你不知道的JavaScript(中卷)》 这本书的介绍吧!