目前世面上有許多方法來部署機器學習模型。最多見的方法是經過 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