目前世面上有许多方法来部署机器学习模型。最多见的方法是经过 API 或 serverless functions 将模型公开为 Web 服务。将模型部署为 Web 服务时,其中一个注意事项是延迟和性能。使用模型基于 HTTP 进行预测的过程包括接受用户输入、从文件中加载模型的序列化版本、使用模型进行预测以及将预测返回给用户。因为模型一般只是静态文件,所以部署模型的另外一种方法是做为 Web 上的静态资产,就像任何其余 HTML、CSS 或 JavaScript 文件同样。此部署方法与 TensorFlow.js 相似。以这种方式进行部署有几个优势。一是再也不有 Web 服务只是为了为模型提供服务,从而使其更具成本效益。另外一个是一旦模型下载到用户的 PC 上,此时使用的资源是用户 PC 的资源,而不是模型原本会托管的服务器。最后,因为模型是静态文件,所以能够经过 CDN 进行分发。html
其中一个挑战是机器学习模型一般使用 JavaScript 之外的语言构建。这使得使用相同的代码/库构建模型变得困难或几乎不可能。WebAssembly 容许 Rust、C++、C# 和其余语言在浏览器中本机运行,从而改变了这一点。有了这种能力,加载模型和进行预测的代码/逻辑就更容易,几乎与本机平台的代码/逻辑至关。Blazor WebAssembly 为用户提供了在 C# 中彻底建立基于组件的现代 Web 应用程序的能力。此外,Blazor WebAssembly 还容许用户以简单且经济高效的方式发布和部署其应用程序做为静态网站。ML.NET是一个开源的跨平台框架,容许开发人员使用 .NET 建立机器学习模型。在这篇文章中,我将演示如何训练一个多分类机器学习模型,预测鸢尾花种类。而后,我将采用该模型并将其与 Blazor WebAssembly 静态网站一块儿部署到 Azure 。此应用程序的完整代码能够在GitHub 上的 MLNETBlazorWASMSample 存储库中找到。git
这个项目是在Windows PC上构建的,但应该在Mac或Linux上执行以体现跨平台特性。github
本文中构建的解决方案包含三个项目:web
使用 .NET CLI 在命令提示符中运行如下命令:算法
dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.2.0-preview1.20073.1
给命名MLNETBlazorWASMSample的解决方案建立新目录。浏览器
mkdir MLNETBlazorWASMSample
导航到新建立的解决方案目录并建立解决方案:安全
cd MLNETBlazorWASMSample
dotnet new sln
模型输入和输出的数据架构在训练期间和进行预测时共享。要共享资源,请建立ConsoleTraining 和 BlazorWebApp 项目共享的类库。在解决方案目录中,输入如下命令:服务器
dotnet new classlib -o SchemaLibrary
安装Microsoft.ML NuGet 包(此解决方案是使用版本 1.4.0 构建的)。整个解决方案都使用Microsoft.ML包。多线程
dotnet add SchemaLibrary package Microsoft.ML
将库项目添加到解决方案。架构
dotnet sln add SchemaLibrary
控制台应用程序包含用于训练模型的一系列数据转换和算法。在解决方案目录中,建立新的控制台应用程序。
dotnet new console -o TrainingConsole
将控制台应用程序添加到解决方案。
dotnet sln add TrainingConsole
引用 SchemaLibrary 项目。
dotnet add TrainingConsole reference SchemaLibrary
Web 应用程序包含一些输入元素,所以用户能够提供模型随后用于进行预测的新数据。在解决方案目录中,建立新的 Blazor WebAssembly 应用程序。
dotnet new blazorwasm -o BlazorWebApp
将 Web 应用程序项目添加到解决方案。
dotnet sln add BlazorWebApp
引用 SchemaLibrary 项目。
dotnet add BlazorWebApp reference SchemaLibrary
用于训练模型的数据来自iris dataset。它包含四个数值列,即花瓣和萼片的度量,最后一列为鸢尾花的种类。这是数据的示例。
Sepal length (cm) | Sepal width (cm) | Petal length (cm) | Petal width (cm) | Class (iris species) |
---|---|---|---|---|
5.1 | 3.5 | 1.4 | 0.2 | Iris-setosa |
7.0 | 3.2 | 4.7 | 1.4 | Iris-versicolor |
6.3 | 3.3 | 6.0 | 2.5 | Iris-virginica |
在SchemaLibrary项目中,建立一个ModelInput类,用于数据建模并做为模型输入。
ni ModelInput.cs
ModelInput类应以下所示:
using Microsoft.ML.Data; namespace SchemaLibrary { public class ModelInput { [LoadColumn(0)] public float SepalLength { get; set; } [LoadColumn(1)] public float SepalWidth { get; set; } [LoadColumn(2)] public float PetalLength { get; set; } [LoadColumn(3)] public float PetalWidth { get; set; } [LoadColumn(4)] public string Label { get; set; } } }
请注意,该列如今是一个称为Class
Label
的属性。缘由有二:
另请注意每一个属性顶部的LoadColumn属性。这用于告诉加载程序列的索引,其中相应属性的数据所在的位置。
与输入架构相似,有模型输出的架构。此解决方案中使用的模型类型是多类分类模型,由于鸢尾花种有两个以上类别可供选择。多分类模型输出称为一个列,该列包含预测类别的名称。在SchemaLibrary项目中,建立一个PredictedLabel
ModelOutput
类,用于对模型所作的预测建模。
ni ModelOutput.cs
ModelOutput类应以下所示:
namespace SchemaLibrary { public class ModelOutput { public string PredictedLabel { get; set; } } }
如今是时候建立训练模型的应用程序了。
下载数据并将其保存在TrainingConsole项目目录中。
curl https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data -o iris.data
在TrainingConsole项目中,打开Program.cs文件,并在顶部添加如下内容:
using System; using System.Linq; using Microsoft.ML; using SchemaLibrary;
而后,删除Main方法内的内容,并将其替换为如下内容。
// 1. Initialize MLContext MLContext mlContext = new MLContext(); // 2. Load the data IDataView data = mlContext.Data.LoadFromTextFile<ModelInput>("iris.data", separatorChar:','); // 3. Shuffle the data IDataView shuffledData = mlContext.Data.ShuffleRows(data); // 3. Define the data preparation and training pipeline. IEstimator<ITransformer> pipeline = mlContext.Transforms.Concatenate("Features","SepalLength","SepalWidth","PetalLength","PetalWidth") .Append(mlContext.Transforms.NormalizeMinMax("Features")) .Append(mlContext.Transforms.Conversion.MapValueToKey("Label")) .Append(mlContext.MulticlassClassification.Trainers.NaiveBayes()) .Append(mlContext.Transforms.Conversion.MapKeyToValue("PredictedLabel")); // 4. Train with cross-validation var cvResults = mlContext.MulticlassClassification.CrossValidate(shuffledData, pipeline); // 5. Get the highest performing model and its accuracy (ITransformer, double) model = cvResults .OrderByDescending(fold => fold.Metrics.MacroAccuracy) .Select(fold => (fold.Model, fold.Metrics.MacroAccuracy)) .First(); Console.WriteLine($"Top performing model's macro-accuracy: {model.Item2}"); // 6. Save the model mlContext.Model.Save(model.Item1, data.Schema, "model.zip"); Console.WriteLine("Model trained");
训练应用程序从文件iris.data加载数据并应用一系列转换。首先,全部单独的数字列都合并到单个矢量中,并存储在称为Features的新列中。而后,该Features列须要预处理归一化,MapValueToKey转换用于将Label列中的文本转换为数字。而后,转换后的数据用于使用NaiveBayes算法训练模型。请注意,在撰写本文时,对于多分类问题,只有 Naive Bayes 已确认与 Blazor WebAssembly 合做。最后,将PredictedLabel存储为数字,以便必须将其转换回文本。
使用Fit方法,将数据应用于管道。因为数据集很小,所以使用称为交叉验证的技术来构建更健壮的模型。训练模型后,具备最高性能的模型将序列化并保存到名为model.zip的文件,以供之后在 Web 应用程序中使用。
最终Program.cs文件应相似于如下内容:
using System; using System.Linq; using Microsoft.ML; using SchemaLibrary; namespace TrainingConsole { class Program { static void Main(string[] args) { // 1. Initialize MLContext MLContext mlContext = new MLContext(); // 2. Load the data IDataView data = mlContext.Data.LoadFromTextFile<ModelInput>("iris.data", separatorChar:','); // 3. Shuffle the data IDataView shuffledData = mlContext.Data.ShuffleRows(data); // 3. Define the data preparation and training pipeline. IEstimator<ITransformer> pipeline = mlContext.Transforms.Concatenate("Features","SepalLength","SepalWidth","PetalLength","PetalWidth") .Append(mlContext.Transforms.NormalizeMinMax("Features")) .Append(mlContext.Transforms.Conversion.MapValueToKey("Label")) .Append(mlContext.MulticlassClassification.Trainers.NaiveBayes()) .Append(mlContext.Transforms.Conversion.MapKeyToValue("PredictedLabel")); // 4. Train with cross-validation var cvResults = mlContext.MulticlassClassification.CrossValidate(shuffledData, pipeline); // 5. Get the highest performing model and its accuracy (ITransformer, double) model = cvResults .OrderByDescending(fold => fold.Metrics.MacroAccuracy) .Select(fold => (fold.Model, fold.Metrics.MacroAccuracy)) .First(); Console.WriteLine($"Top performing model's macro-accuracy: {model.Item2}"); // 6. Save the model mlContext.Model.Save(model.Item1, data.Schema, "model.zip"); Console.WriteLine("Model trained"); } } }
在TrainConsole项目目录中,使用如下命令运行应用程序并训练模型:
dotnet run
保存模型后,使用 Azure 门户建立 Azure 存储账户。
而后,导航到新建立的存储账户资源,并建立名为models的 Blob 容器。
建立容器后,导航到它并上传model.zip文件。
要进行预测,请建立一个网页以获取用户输入。而后向模型提供用户输入,并将预测显示给用户。
在BlazorWebApp项目目录中,打开_Imports.razor文件。这包含应用程序中页面和组件的 using 语句。添加如下使用语句:
@using System.IO
@using Microsoft.ML
@using SchemaLibrary
在BlazorWebApp项目中,在Pages目录中建立一个名为"Prediction.razor"的新razor页面。
ni Prediction.razor
向其中添加如下内容:
@page "/prediction" @inject HttpClient _client <label>Sepal Length: </label> <input type="text" @bind="_sepalLength"><br> <label>Sepal Width: </label> <input type="text" @bind="_sepalWidth"><br> <label>Petal Length: </label> <input type="text" @bind="_petalLength"><br> <label>Petal Width: </label> <input type="text" @bind="_petalWidth"><br> <button @onclick="GetPrediction">Make prediction</button> @if(@ModelPrediction == null) { <p>Enter data to get a prediction</p> } else { <p>@ModelPrediction</p> } @code { private PredictionEngine<ModelInput,ModelOutput> _predictionEngine; private string _sepalLength, _sepalWidth, _petalLength, _petalWidth, ModelPrediction; protected override async Task OnInitializedAsync() { Stream savedModel = await _client.GetStreamAsync("<YOUR-MODEL-ENDPOINT>"); MLContext mlContext = new MLContext(); ITransformer _model = mlContext.Model.Load(savedModel,out DataViewSchema schema); _predictionEngine = mlContext.Model.CreatePredictionEngine<ModelInput,ModelOutput>(_model); } private void GetPrediction() { ModelInput input = new ModelInput { SepalLength=float.Parse(_sepalLength), SepalWidth=float.Parse(_sepalWidth), PetalLength=float.Parse(_petalLength), PetalWidth=float.Parse(_petalWidth) }; ModelOutput prediction = _predictionEngine.Predict(input); ModelPrediction = prediction.PredictedLabel; } }
Prediction.razor页包含模型原始训练的每一个列的文本输入元素。初始化页面时,将从 Azure 存储加载模型并建立PredictionEngine。请确保将<YOUR-MODEL-ENDPOINT>
替换为包含模型
的 blob 的 URL。PredictionEngine是进行单个预测的便利 API。传统上,当模型用做 Web 服务时,建议使用该PredictionEnginePool服务,由于它在多线程应用程序中具备线程安全且性能更高。可是,在这种状况下,因为模型被下载到单个用户的浏览器上,所以可使用PredictionEngine。用户输入输入值并单击"建立预测"按钮后,该GetPrediction方法经过获取用户输入并使用PredictionEngine进行预测来执行。而后,预测将显示在浏览器中。
在BlazorWebApp项目中,在Shared目录中打开NavMenu.razor文件。
将如下列表项添加到<ul>元素。
<li class="nav-item px-3"> <NavLink class="nav-link" href="prediction"> <span class="oi oi-list-rich" aria-hidden="true"></span> Prediction </NavLink> </li>
最终的NavMenu.razor页面应以下所示:
<div class="top-row pl-4 navbar navbar-dark"> <a class="navbar-brand" href="">BlazorWebApp</a> <button class="navbar-toggler" @onclick="ToggleNavMenu"> <span class="navbar-toggler-icon"></span> </button> </div> <div class="@NavMenuCssClass" @onclick="ToggleNavMenu"> <ul class="nav flex-column"> <li class="nav-item px-3"> <NavLink class="nav-link" href="" Match="NavLinkMatch.All"> <span class="oi oi-home" aria-hidden="true"></span> Home </NavLink> </li> <li class="nav-item px-3"> <NavLink class="nav-link" href="counter"> <span class="oi oi-plus" aria-hidden="true"></span> Counter </NavLink> </li> <li class="nav-item px-3"> <NavLink class="nav-link" href="fetchdata"> <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data </NavLink> </li> <li class="nav-item px-3"> <NavLink class="nav-link" href="prediction"> <span class="oi oi-list-rich" aria-hidden="true"></span> Prediction </NavLink> </li> </ul> </div> @code { private bool collapseNavMenu = true; private string NavMenuCssClass => collapseNavMenu ? "collapse" : null; private void ToggleNavMenu() { collapseNavMenu = !collapseNavMenu; } }
Web 应用程序将做为 Azure 存储上的静态站点托管。
在 Azure 门户中,导航到托管模型的存储账户资源。
为存储账户启用静态网站,并将索引文档名称和错误文档路径设置为index.html。
此时,在存储账户中建立了名为$web的新容器。这是您网站的全部静态文件将驻留的位置。此外,还会建立一个主终结点。这是您将用于访问应用程序的 URL
存储账户具备一些默认的 CORS 设置。为了从应用程序下载和使用模型,您必须对其进行配置。
对于"Allowed origins",请输入主终结点。
要发布应用程序,请运行如下命令:
dotnet publish -c Release
这将生成在BlazorWebApp项目的 bin/Release/netstandard2.1/publish/BlazorWebApp/dist 目录中所需的文件托管为Web 应用程序静态网站。
要部署应用程序,请使用 Azure 存储资源管理器将dist目录中的全部文件复制到 Azure 存储账户的$web容器中。
在浏览器中,导航到静态网站的主终结点,而后选择"Prediction"页。输入数据并单击"Make prediction"。页面应以下所示。
您可能会注意到,Naive Bayes 在此数据集上的性能不是最好的,所以某些预测可能不太准确。我如今能够接受这一点,由于这是一个概念验证,以展现这些技术如何协同工做。也许使用更好的数据集能够产生更好的结果。
在这篇文章中,我考虑如何将ML.NET多分类模型与 Blazor WebAssembly 静态网站一块儿部署到 Azure。虽然因为 WebAssembly 和 Blazor WebAssembly 的早期阶段,与其余部署方法相比,这一点更为有限,但这代表了这些技术的可能性。以这种方式部署可减小部署这些模型所需的资源量,并将处理从服务器或 Web 服务转移到客户端的浏览器,从而使机器学习模型的部署和分发更加高效、可扩展且更具成本效益。
SchemaLibrary