使用 Windows 機器學習 API 在 Windows 應用程式中部署模型
在本教學課程的上一個部分中,您已瞭解如何以 ONNX 格式建置和導出模型。 現在您已擁有該模型,您可以藉由呼叫 WinML API,將其內嵌至 Windows 應用程式,並在裝置本機執行。
完成之後,您將會有一個工作影像分類器 WinML UWP app (C#)。
關於範例應用程式
使用我們的模型,我們將建立可分類食物影像的應用程式。 它可讓您從本機裝置選取影像,並透過您在上一個部分中建置和定型的本機儲存分類 ONNX 模型來處理映像。 傳回的標記會顯示在影像旁邊,以及分類的信賴機率。
如果您到目前為止已遵循本教學課程,就應該已具備應用程式開發所需的必要條件。 如果您需要重新整理程式,請參閱 本教學課程的第一個部分。
注意
如果您要下載完整的範例程式代碼,您可以複製 方案檔。 複製存放庫,流覽至此範例,然後使用 Visual Studio 開啟 ImageClassifierAppUWP.sln
檔案。 然後,您可以跳至 [啟動應用程式](#Launch 應用程式) 步驟。
建立 WinML UWP (C#)
以下我們將示範如何從頭開始建立您的應用程式和 WinML 程式代碼。 您將了解如何:
- 載入機器學習模型。
- 以所需的格式載入影像。
- 系結模型的輸入和輸出。
- 評估模型並顯示有意義的結果。
您也會使用基本 XAML 來建立簡單的 GUI,以便測試影像分類器。
建立 應用程式
- 開啟 Visual Studio,然後選擇
create a new project
。
- 在搜尋欄中,輸入
UWP
,然後選取Blank APP (Universal Windows
。 這會為沒有預先定義控件或版面配置的單頁 通用 Windows 平台 (UWP) 應用程式開啟新的 C# 專案。 選取Next
以開啟專案的組態視窗。
- 在組態視窗中:
- 為您的項目選擇名稱。 在這裡,我們使用 ImageClassifierAppUWP。
- 選擇專案的位置。
- 如果您使用 VS 2019,請確定
Place solution and project in the same directory
未核取。 - 如果您使用 VS 2017,請確定
Create directory for solution
已核取 。
按 create
以建立您的專案。 最低目標版本視窗可能會快顯。 請確定您的最低版本已設定為 Windows 10 組建 17763 或更新版本。
若要建立應用程式並使用 WinML 應用程式部署模型,您需要下列專案:
建立項目之後,流覽至專案資料夾、開啟 assets 資料夾 [....\ImageClassifierAppUWP\Assets],然後將模型複製到此位置。
將模型名稱從
model.onnx
變更為classifier.onnx
。 這讓事情變得更清楚,並將它對齊教學課程的格式。
探索您的模型
讓我們熟悉模型檔案的結構。
使用 Netron 開啟您的
classifier.onnx
模型檔案。按
Data
以開啟模型屬性。
如您所見,模型需要 32 位 Tensor (多維度陣列) float 物件做為輸入,並傳回兩個輸出:第一個命名為字串的張量,而第二個名稱classLabel
loss
是字元串對浮點數對應序列,描述每個標記分類的機率。 您需要此資訊,才能在 Windows 應用程式中成功顯示模型輸出。
探索專案方案
讓我們探索您的專案方案。
Visual Studio 會自動在 方案總管 內建立數個 cs 程式代碼檔案。 MainPage.xaml
包含 GUI 的 XAML 程式代碼,並 MainPage.xaml.cs
包含您的應用程式程式碼。 如果您之前已建立 UWP 應用程式,則這些檔案應該非常熟悉。
建立應用程式 GUI
首先,讓我們為您的應用程式建立簡單的 GUI。
按兩下檔案
MainPage.xaml
。 在您的空白應用程式中,應用程式的 GUI XAML 範本是空的,因此我們需要新增一些 UI 功能。將下列程式代碼新增至的
MainPage.xaml
主體。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Margin="1,0,-1,0">
<TextBlock x:Name="Menu"
FontWeight="Bold"
TextWrapping="Wrap"
Margin="10,0,0,0"
Text="Image Classification"/>
<TextBlock Name="space" />
<Button Name="recognizeButton"
Content="Pick Image"
Click="OpenFileButton_Click"
Width="110"
Height="40"
IsEnabled="True"
HorizontalAlignment="Left"/>
<TextBlock Name="space3" />
<Button Name="Output"
Content="Result is:"
Width="110"
Height="40"
IsEnabled="True"
HorizontalAlignment="Left"
VerticalAlignment="Top">
</Button>
<!--Display the Result-->
<TextBlock Name="displayOutput"
FontWeight="Bold"
TextWrapping="Wrap"
Margin="30,0,0,0"
Text="" Width="1471" />
<Button Name="ProbabilityResult"
Content="Probability is:"
Width="110"
Height="40"
IsEnabled="True"
HorizontalAlignment="Left"/>
<!--Display the Result-->
<TextBlock Name="displayProbability"
FontWeight="Bold"
TextWrapping="Wrap"
Margin="30,0,0,0"
Text="" Width="1471" />
<TextBlock Name="space2" />
<!--Image preview -->
<Image Name="UIPreviewImage" Stretch="Uniform" MaxWidth="300" MaxHeight="300"/>
</StackPanel>
</Grid>
Windows Machine Learning 程式碼產生器
Windows 機器學習 程式代碼產生器或 mlgen 是 Visual Studio 擴充功能,可協助您開始使用 UWP app 上的 WinML API。 當您將定型的 ONNX 檔案新增至 UWP 專案時,它會產生範本程式代碼。
Windows 機器學習 的程式代碼產生器 mlgen 會建立介面(適用於 C#、C++/WinRT 和 C++/CX),其中包含為您呼叫 Windows ML API 的包裝函式類別。 這可讓您輕鬆地載入、系結及評估專案中的模型。 在本教學課程中,我們將使用它來處理其中許多函式。
程式代碼產生器適用於 Visual Studio 2017 和更新版本。 請注意,在 Windows 10 版本 1903 和更新版本中,mlgen 不再包含在 Windows 10 SDK 中,因此您必須下載並安裝擴充功能。 如果您已遵循本教學課程的簡介,表示您已經處理過此動作,但如果不是,您應該下載 VS 2019 或 VS 2017。
注意
若要深入瞭解 mlgen,請參閱 mlgen 檔
如果您尚未安裝 mlgen,請安裝 mlgen。
以滑鼠右鍵按下
Assets
Visual Studio 中 方案總管 的資料夾,然後選取Add > Existing Item
。瀏覽至 內的
ImageClassifierAppUWP [….\ImageClassifierAppUWP\Assets]
assets 資料夾,尋找您先前在該處複製的 ONNX 模型,然後選取add
。將 ONNX 模型 (名稱: “classifier”) 新增至 VS 中方案總管中的 assets 資料夾之後,專案現在應該會有兩個新的檔案:
classifier.onnx
- 這是您的模型,其格式為 ONNX。classifier.cs
– 自動產生的 WinML 程式代碼檔案。
- 若要確定當您編譯應用程式時模型會建置,請選擇檔案
classifier.onnx
並選擇Properties
。 針對Build Action
,選取Content
。
現在,讓我們探索classifier.cs檔案中新產生的程序代碼。
產生的程式代碼包含三個類別:
classifierModel
:這個類別包含兩個模型具現化和模型評估的方法。 其可協助我們建立機器學習模型表示法、在系統默認裝置上建立會話、將特定輸入和輸出系結至模型,以及以異步方式評估模型。classifierInput
:這個類別會初始化模型預期的輸入類型。 模型輸入取決於輸入數據的模型需求。 在我們的案例中,輸入需要 ImageFeatureValue,此類別描述用來傳遞至模型的影像屬性。classifierOutput
:這個類別會初始化模型將輸出的類型。 模型輸出取決於模型定義的方式。 在我們的案例中,輸出將是 String 和 TensorFloat 類型為 map (dictionaries) 的序列,稱為 loss。
您現在會使用這些類別來載入、系結和評估專案中的模型。
載入模型和輸入
載入模型
按兩下程式
MainPage.xaml.cs
代碼檔案以開啟應用程式程式代碼。將 「using」 語句取代為下列專案,以取得您需要之所有 API 的存取權。
// Specify all the using statements which give us the access to all the APIs that you'll need
using System;
using System.Threading.Tasks;
using Windows.AI.MachineLearning;
using Windows.Graphics.Imaging;
using Windows.Media;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
- 在類別內的using語句之後,於命名空間
ImageClassifierAppUWP
底下MainPage
新增下列變數宣告。
// All the required variable declaration
private classifierModel modelGen;
private classifierInput input = new classifierModelInput();
private classifierOutput output;
private StorageFile selectedStorageFile;
private string result = "";
private float resultProbability = 0;
結果如下所示。
// Specify all the using statements which give us the access to all the APIs that we'll need
using System;
using System.Threading.Tasks;
using Windows.AI.MachineLearning;
using Windows.Graphics.Imaging;
using Windows.Media;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
namespace ImageClassifierAppUWP
{
public sealed partial class MainPage : Page
{
// All the required fields declaration
private classifierModel modelGen;
private classifierInput input = new classifierInput();
private classifierOutput output;
private StorageFile selectedStorageFile;
private string result = "";
private float resultProbability = 0;
現在,您將實作 LoadModel
方法。 方法會存取 ONNX 模型,並將其儲存在記憶體中。 然後,您將使用 CreateFromStreamAsync
方法將模型具現化為 LearningModel
物件。 類別 LearningModel
代表定型的機器學習模型。 具現化之後, LearningModel
就是您用來與 Windows ML 互動的初始物件。
若要載入模型,您可以在 類別中使用 LearningModel
數個靜態方法。 在此情況下,您將使用 CreateFromStreamAsync
方法。
方法 CreateFromStreamAsync
是使用 mlgen 自動建立的,因此您不需要實作這個方法。 您可以按兩下 mlgen 產生的檔案, classifier.cs
以檢閱此方法。
若要深入了解 LearningModel
課程,請檢閱 LearningModel 類別檔。
若要深入瞭解載入模型的其他方式,請檢閱 載入模型檔
loadModel
將方法新增至 類別內的MainPage
程式MainPage.xaml.cs
碼檔案。
private async Task loadModel()
{
// Get an access the ONNX model and save it in memory.
StorageFile modelFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri($"ms-appx:///Assets/classifier.onnx"));
// Instantiate the model.
modelGen = await classifierModel.CreateFromStreamAsync(modelFile);
}
- 現在,將對新方法的呼叫新增至 類別的建構函式。
// The main page to initialize and execute the model.
public MainPage()
{
this.InitializeComponent();
loadModel();
}
結果如下所示。
// The main page to initialize and execute the model.
public MainPage()
{
this.InitializeComponent();
loadModel();
}
// A method to load a machine learning model.
private async Task loadModel()
{
// Get an access the ONNX model and save it in memory.
StorageFile modelFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri($"ms-appx:///Assets/classifier.onnx"));
// Instantiate the model.
modelGen = await classifierModel.CreateFromStreamAsync(modelFile);
}
載入影像
- 我們需要定義 click 事件,以起始模型執行的四個方法呼叫序列 – 轉換、系結和評估、輸出擷取和顯示結果。 將下列方法新增至 類別內的
MainPage
程式MainPage.xaml.cs
碼檔案。
// Waiting for a click event to select a file
private async void OpenFileButton_Click(object sender, RoutedEventArgs e)
{
if (!await getImage())
{
return;
}
// After the click event happened and an input selected, begin the model execution.
// Bind the model input
await imageBind();
// Model evaluation
await evaluate();
// Extract the results
extractResult();
// Display the results
await displayResult();
}
- 現在,您將實作
getImage()
方法。 此方法會選取輸入圖像檔,並將它儲存在記憶體中。 將下列方法新增至 類別內的MainPage
程式MainPage.xaml.cs
碼檔案。
// A method to select an input image file
private async Task<bool> getImage()
{
try
{
// Trigger file picker to select an image file
FileOpenPicker fileOpenPicker = new FileOpenPicker();
fileOpenPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
fileOpenPicker.FileTypeFilter.Add(".jpg");
fileOpenPicker.FileTypeFilter.Add(".png");
fileOpenPicker.ViewMode = PickerViewMode.Thumbnail;
selectedStorageFile = await fileOpenPicker.PickSingleFileAsync();
if (selectedStorageFile == null)
{
return false;
}
}
catch (Exception)
{
return false;
}
return true;
}
現在,您將實作 image Bind()
方法,以取得位圖 BGRA8 格式的檔案表示法。
- 將方法的實作
convert()
新增至 MainPage 類別內的程式MainPage.xaml.cs
碼檔案。 convert 方法會以 BGRA8 格式取得輸入檔的表示法。
// A method to convert and bind the input image.
private async Task imageBind()
{
UIPreviewImage.Source = null;
try
{
SoftwareBitmap softwareBitmap;
using (IRandomAccessStream stream = await selectedStorageFile.OpenAsync(FileAccessMode.Read))
{
// Create the decoder from the stream
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);
// Get the SoftwareBitmap representation of the file in BGRA8 format
softwareBitmap = await decoder.GetSoftwareBitmapAsync();
softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
}
// Display the image
SoftwareBitmapSource imageSource = new SoftwareBitmapSource();
await imageSource.SetBitmapAsync(softwareBitmap);
UIPreviewImage.Source = imageSource;
// Encapsulate the image within a VideoFrame to be bound and evaluated
VideoFrame inputImage = VideoFrame.CreateWithSoftwareBitmap(softwareBitmap);
// bind the input image
ImageFeatureValue imageTensor = ImageFeatureValue.CreateFromVideoFrame(inputImage);
input.data = imageTensor;
}
catch (Exception e)
{
}
}
本節中完成的工作結果如下所示。
// Waiting for a click event to select a file
private async void OpenFileButton_Click(object sender, RoutedEventArgs e)
{
if (!await getImage())
{
return;
}
// After the click event happened and an input selected, we begin the model execution.
// Bind the model input
await imageBind();
// Model evaluation
await evaluate();
// Extract the results
extractResult();
// Display the results
await displayResult();
}
// A method to select an input image file
private async Task<bool> getImage()
{
try
{
// Trigger file picker to select an image file
FileOpenPicker fileOpenPicker = new FileOpenPicker();
fileOpenPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
fileOpenPicker.FileTypeFilter.Add(".jpg");
fileOpenPicker.FileTypeFilter.Add(".png");
fileOpenPicker.ViewMode = PickerViewMode.Thumbnail;
selectedStorageFile = await fileOpenPicker.PickSingleFileAsync();
if (selectedStorageFile == null)
{
return false;
}
}
catch (Exception)
{
return false;
}
return true;
}
// A method to convert and bind the input image.
private async Task imageBind()
{
UIPreviewImage.Source = null;
try
{
SoftwareBitmap softwareBitmap;
using (IRandomAccessStream stream = await selectedStorageFile.OpenAsync(FileAccessMode.Read))
{
// Create the decoder from the stream
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);
// Get the SoftwareBitmap representation of the file in BGRA8 format
softwareBitmap = await decoder.GetSoftwareBitmapAsync();
softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
}
// Display the image
SoftwareBitmapSource imageSource = new SoftwareBitmapSource();
await imageSource.SetBitmapAsync(softwareBitmap);
UIPreviewImage.Source = imageSource;
// Encapsulate the image within a VideoFrame to be bound and evaluated
VideoFrame inputImage = VideoFrame.CreateWithSoftwareBitmap(softwareBitmap);
// bind the input image
ImageFeatureValue imageTensor = ImageFeatureValue.CreateFromVideoFrame(inputImage);
input.data = imageTensor;
}
catch (Exception e)
{
}
}
系結和評估模型
接下來,您將根據模型建立會話、系結會話的輸入和輸出,以及評估模型。
建立會話以系結模型:
若要建立會話,您可以使用 類別 LearningModelSession
。 這個類別可用來評估機器學習模型,並將模型系結至裝置,然後執行並評估模型。 當您建立會話以在機器的特定裝置上執行模型時,您可以選取裝置。 默認裝置是 CPU。
注意
若要深入瞭解如何選擇裝置,請檢閱 建立會話 檔。
系結模型輸入和輸出:
若要系結輸入和輸出,您可以使用 類別 LearningModelBinding
。 機器學習模型具有輸入和輸出功能,可將資訊傳入和傳出模型。 請注意,Windows ML API 必須支援必要的功能。 類別 LearningModelBinding
會套用至 LearningModelSession
,以將值系結至具名輸入和輸出功能。
系結的實作由 mlgen 自動產生,因此您不需要處理它。 系結是藉由呼叫 類別的預先定義方法來實作 LearningModelBinding
。 在我們的案例中 Bind
,它會使用 方法將值系結至具名功能類型。
目前,Windows ML 支援所有 ONNX 功能類型,例如 Tensors(多維度陣列)、序列(值向量)、地圖(資訊值組)和影像(特定格式)。 所有影像都會以張量格式以 Windows ML 表示。 張量化程序會將影像轉換成張量,並且會在繫結期間進行。
幸運的是,您不需要處理張量轉換。 ImageFeatureValue
您在上一個部分中使用的方法會同時處理轉換和張量,因此影像符合模型所需的影像格式。
注意
若要深入瞭解如何系結 LearningModel
和 WinML 支援的功能類型,請參閱 系結模型 檔。
評估模型:
建立工作階段以將模型和系結值系結至模型的輸入和輸出之後,您可以評估模型的輸入並取得其預測。 若要執行模型執行,您應該在 LearningModelSession 上呼叫任何預先定義的評估方法。 在我們的案例中,我們將使用 EvaluateAsync
方法。
類似於 CreateFromStreamAsync
, EvaluateAsync
此方法也由 WinML 程式代碼產生器自動產生,因此您不需要實作這個方法。 您可以在 檔案中 classifier.cs
檢閱此方法。
方法 EvaluateAsync
會使用已系結於系結的功能值,以異步方式評估機器學習模型。 它會使用 建立會話 LearningModelSession
、使用 系結輸入和輸出 LearningModelBinding
、執行模型評估,並使用 類別取得模型的 LearningModelEvaluationResult
輸出功能。
注意
若要了解執行模型的其他評估方法,請檢閱 LearningModelSession 類別文件,檢查哪些方法可以在 LearningModelSession 上實作。
- 將下列方法新增至 MainPage 類別內的程式
MainPage.xaml.cs
碼檔案,以建立工作階段、系結和評估模型。
// A method to evaluate the model
private async Task evaluate()
{
output = await modelGen.EvaluateAsync(input);
}
擷取並顯示結果
您現在必須擷取模型輸出並顯示正確的結果。 您將藉由實 extractResult
作 和 displayResult
方法來執行此動作。
如您稍早探索,模型會傳回兩個輸出:第一個命名 classLabel
為字串的張量,而第二個具名損失則是一連串字串對浮點數對應,描述每個標記分類的機率。 因此,若要成功顯示結果和機率,我們只需要從遺失輸出擷取輸出。 我們需要找出最高的機率,才能傳回正確的結果。
- 將
extractResult
方法新增至 類別內的MainPage
程式MainPage.xaml.cs
碼檔案。
private void extractResult()
{
// A method to extract output (result and a probability) from the "loss" output of the model
var collection = output.loss;
float maxProbability = 0;
string keyOfMax = "";
foreach (var dictionary in collection)
{
foreach (var key in dictionary.Keys)
{
if (dictionary[key] > maxProbability)
{
maxProbability = dictionary[key];
keyOfMax = key;
}
}
}
result = keyOfMax;
resultProbability = maxProbability;
}
- 將
displayResult
方法新增至 類別內的MainPage
程式MainPage.xaml.cs
碼檔案。
// A method to display the results
private async Task displayResult()
{
displayOutput.Text = result.ToString();
displayProbability.Text = resultProbability.ToString();
}
Bind 和 Evaluate 的結果和擷取,並顯示我們應用程式 WinML 程式代碼的結果部分會如下所示。
// A method to evaluate the model
private async Task evaluate()
{
output = await modelGen.EvaluateAsync(input);
}
// A method to extract output (string and a probability) from the "loss" output of the model
private void extractResult()
{
var collection = output.loss;
float maxProbability = 0;
string keyOfMax = "";
foreach (var dictionary in collection)
{
foreach (var key in dictionary.Keys)
{
if (dictionary[key] > maxProbability)
{
maxProbability = dictionary[key];
keyOfMax = key;
}
}
}
result = keyOfMax;
resultProbability = maxProbability;
}
// A method to display the results
private async Task displayResult()
{
displayOutput.Text = result.ToString();
displayProbability.Text = resultProbability.ToString();
}
介紹完畢! 您已成功使用基本 GUI 建立 Windows 機器學習應用程式,以測試分類模型。 下一個步驟是啟動應用程式,並在您的 Windows 裝置本機執行。
啟動應用程式
完成應用程式介面、新增模型併產生 WinML 程式代碼之後,您就可以測試應用程式。 確定頂端工具列中的下拉選單設定為 Debug
。 Solution Platform
x64
如果您的裝置是64位,或 x86
32位,請將變更為 ,以在本機電腦上執行專案。
若要測試我們的應用程式,您將使用水果的下圖。 讓我們看看我們的 app 如何分類影像的內容。
將此映像儲存在您的本機裝置上,以測試應用程式。 視需要將影像格式變更為 jpg。 您也可以以適當的格式從本機裝置新增任何其他相關影像 - .jpg、.png、.bmp或.gif格式。
若要執行專案,請按
Start Debugging
工具列上的按鈕,或按F5
。當應用程式啟動時,按
Pick Image
並選取本機裝置中的映像。
結果會立即出現在畫面上。 如您所見,我們的 WinML 應用程式已成功將影像分類為水果或蔬菜,信賴評等為 99.9%。
摘要
您剛建立第一個 Windows 機器學習 應用程式,從模型建立到成功執行。
其他資源
若要深入了解此教學課程中提及的主題,請瀏覽下列資源:
- Windows ML 工具:深入瞭解 Windows ML 儀錶板、WinMLRunner 和 mglen Windows ML 程式代碼產生器等工具。
- ONNX 模型:深入瞭解 ONNX 格式。
- Windows ML 效能和記憶體:深入瞭解如何使用 Windows ML 管理應用程式效能。
- Windows 機器學習 API 參考:深入瞭解 Windows ML API 的三個領域。