介紹
目前世面上有許多方法來部署機器學習模型。最常見的方法是通過 API 或 serverless functions 將模型公開為 Web 服務。將模型部署為 Web 服務時,其中一個注意事項是延遲和性能。使用模型基於 HTTP 進行預測的過程包括接受用戶輸入、從文件中加載模型的序列化版本、使用模型進行預測以及將預測返回給用戶。由於模型通常只是靜態文件,因此部署模型的另一種方法是作為 Web 上的靜態資產,就像任何其他 HTML、CSS 或 JavaScript 文件一樣。此部署方法與 TensorFlow.js 類似。以這種方式進行部署有幾個優點。一是不再有 Web 服務只是為了為模型提供服務,從而使其更具成本效益。另一個是一旦模型下載到用戶的 PC 上,此時使用的資源是用戶 PC 的資源,而不是模型本來會托管的服務器。最后,由於模型是靜態文件,因此可以通過 CDN 進行分發。
其中一個挑戰是機器學習模型通常使用 JavaScript 以外的語言構建。這使得使用相同的代碼/庫構建模型變得困難或幾乎不可能。WebAssembly 允許 Rust、C++、C# 和其他語言在瀏覽器中本機運行,從而改變了這一點。有了這種能力,加載模型和進行預測的代碼/邏輯就更容易,幾乎與本機平台的代碼/邏輯相當。Blazor WebAssembly 為用戶提供了在 C# 中完全創建基於組件的現代 Web 應用程序的能力。此外,Blazor WebAssembly 還允許用戶以簡單且經濟高效的方式發布和部署其應用程序作為靜態網站。ML.NET是一個開源的跨平台框架,允許開發人員使用 .NET 創建機器學習模型。在這篇文章中,我將演示如何訓練一個多分類機器學習模型,預測鳶尾花種類。然后,我將采用該模型並將其與 Blazor WebAssembly 靜態網站一起部署到 Azure 。此應用程序的完整代碼可以在GitHub 上的 MLNETBlazorWASMSample 存儲庫中找到。
先決條件
這個項目是在Windows PC上構建的,但應該在Mac或Linux上執行以體現跨平台特性。
設置解決方案
本文中構建的解決方案包含三個項目:
- SchemaLibrary:C# .NET 標准 2.0 類庫,其中包含用於訓練模型的數據的架構定義類以及模型生成的預測輸出。
- TrainingConsole:用於訓練機器學習模型的 C# .NET Core 3.1 控制台應用程序。
- BlazorWebApp:Blazor WebAssembly Web 應用程序,使用由TrainingConsole應用程序訓練的機器學習模型進行預測。
安裝Blazor WebAssembly模板
使用 .NET CLI 在命令提示符中運行以下命令:
dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.2.0-preview1.20073.1
創建解決方案
給命名MLNETBlazorWASMSample的解決方案創建新目錄。
mkdir MLNETBlazorWASMSample
導航到新創建的解決方案目錄並創建解決方案:
cd MLNETBlazorWASMSample
dotnet new sln
創建schema類庫
模型輸入和輸出的數據架構在訓練期間和進行預測時共享。要共享資源,請創建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
創建 Blazor WebAssembly Web 應用程序
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
的屬性。原因有二:
- 避免使用class關鍵字。
- 在ML.NET中,算法要預測的列的默認列名稱為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 應用程序
Web 應用程序將作為 Azure 存儲上的靜態站點托管。
在 Azure 門戶中,導航到托管模型的存儲帳戶資源。
啟用靜態網站
為存儲帳戶啟用靜態網站,並將索引文檔名稱和錯誤文檔路徑設置為index.html。
此時,在存儲帳戶中創建了名為$web的新容器。這是您網站的所有靜態文件將駐留的位置。此外,還會創建一個主終結點。這是您將用於訪問應用程序的 URL
配置 CORS
存儲帳戶具有一些默認的 CORS 設置。為了從應用程序下載和使用模型,您必須對其進行配置。
對於"Allowed origins",請輸入主終結點。
發布和部署 Web 應用程序
要發布應用程序,請運行以下命令:
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