共用方式為


使用 Windows 機器學習 API 在 Windows 應用程式中部署模型

在本教學課程的上一個部分中,您已瞭解如何以 ONNX 格式建置和導出模型。 現在您已擁有該模型,您可以藉由呼叫 WinML API,將其內嵌至 Windows 應用程式,並在裝置本機執行。

完成之後,您將會有一個工作影像分類器 WinML UWP app (C#)。

關於範例應用程式

使用我們的模型,我們將建立可分類食物影像的應用程式。 它可讓您從本機裝置選取影像,並透過您在上一個部分中建置和定型的本機儲存分類 ONNX 模型來處理映像。 傳回的標記會顯示在影像旁邊,以及分類的信賴機率。

如果您到目前為止已遵循本教學課程,就應該已具備應用程式開發所需的必要條件。 如果您需要重新整理程式,請參閱 本教學課程的第一個部分。

注意

如果您要下載完整的範例程式代碼,您可以複製 方案檔。 複製存放庫,流覽至此範例,然後使用 Visual Studio 開啟 ImageClassifierAppUWP.sln 檔案。 然後,您可以跳至 [啟動應用程式](#Launch 應用程式) 步驟。

建立 WinML UWP (C#)

以下我們將示範如何從頭開始建立您的應用程式和 WinML 程式代碼。 您將了解如何:

  • 載入機器學習模型。
  • 以所需的格式載入影像。
  • 系結模型的輸入和輸出。
  • 評估模型並顯示有意義的結果。

您也會使用基本 XAML 來建立簡單的 GUI,以便測試影像分類器。

建立 應用程式

  1. 開啟 Visual Studio,然後選擇 create a new project

Create a new Visual Studio project

  1. 在搜尋欄中,輸入 UWP ,然後選取 Blank APP (Universal Windows。 這會為沒有預先定義控件或版面配置的單頁 通用 Windows 平台 (UWP) 應用程式開啟新的 C# 專案。 選取 Next 以開啟專案的組態視窗。

Create a new UWP app

  1. 在組態視窗中:
  • 為您的項目選擇名稱。 在這裡,我們使用 ImageClassifierAppUWP
  • 選擇專案的位置。
  • 如果您使用 VS 2019,請確定 Place solution and project in the same directory 未核取。
  • 如果您使用 VS 2017,請確定 Create directory for solution 已核取 。

create 以建立您的專案。 最低目標版本視窗可能會快顯。 請確定您的最低版本已設定為 Windows 10 組建 17763 或更新版本。

若要建立應用程式並使用 WinML 應用程式部署模型,您需要下列專案:

  1. 建立項目之後,流覽至專案資料夾、開啟 assets 資料夾 [....\ImageClassifierAppUWP\Assets],然後將模型複製到此位置。

  2. 將模型名稱從 model.onnx 變更為 classifier.onnx。 這讓事情變得更清楚,並將它對齊教學課程的格式。

探索您的模型

讓我們熟悉模型檔案的結構。

  1. 使用 Netron 開啟您的classifier.onnx模型檔案。

  2. Data 以開啟模型屬性。

Model properties

如您所見,模型需要 32 位 Tensor (多維度陣列) float 物件做為輸入,並傳回兩個輸出:第一個命名為字串的張量,而第二個名稱classLabelloss是字元串對浮點數對應序列,描述每個標記分類的機率。 您需要此資訊,才能在 Windows 應用程式中成功顯示模型輸出。

探索專案方案

讓我們探索您的專案方案。

Visual Studio 會自動在 方案總管 內建立數個 cs 程式代碼檔案。 MainPage.xaml 包含 GUI 的 XAML 程式代碼,並 MainPage.xaml.cs 包含您的應用程式程式碼。 如果您之前已建立 UWP 應用程式,則這些檔案應該非常熟悉。

建立應用程式 GUI

首先,讓我們為您的應用程式建立簡單的 GUI。

  1. 按兩下檔案 MainPage.xaml 。 在您的空白應用程式中,應用程式的 GUI XAML 範本是空的,因此我們需要新增一些 UI 功能。

  2. 將下列程式代碼新增至的 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 2019VS 2017

注意

若要深入瞭解 mlgen,請參閱 mlgen 檔

  1. 如果您尚未安裝 mlgen,請安裝 mlgen。

  2. 以滑鼠右鍵按下 Assets Visual Studio 中 方案總管 的資料夾,然後選取 Add > Existing Item

  3. 瀏覽至 內的 ImageClassifierAppUWP [….\ImageClassifierAppUWP\Assets]assets 資料夾,尋找您先前在該處複製的 ONNX 模型,然後選取 add

  4. 將 ONNX 模型 (名稱: “classifier”) 新增至 VS 中方案總管中的 assets 資料夾之後,專案現在應該會有兩個新的檔案:

  • classifier.onnx - 這是您的模型,其格式為 ONNX。
  • classifier.cs – 自動產生的 WinML 程式代碼檔案。

Project structure with ONNX model added

  1. 若要確定當您編譯應用程式時模型會建置,請選擇檔案 classifier.onnx 並選擇 Properties。 針對 Build Action,選取 Content

現在,讓我們探索classifier.cs檔案中新產生的程序代碼。

產生的程式代碼包含三個類別:

  • classifierModel:這個類別包含兩個模型具現化和模型評估的方法。 其可協助我們建立機器學習模型表示法、在系統默認裝置上建立會話、將特定輸入和輸出系結至模型,以及以異步方式評估模型。
  • classifierInput:這個類別會初始化模型預期的輸入類型。 模型輸入取決於輸入數據的模型需求。 在我們的案例中,輸入需要 ImageFeatureValue,此類別描述用來傳遞至模型的影像屬性。
  • classifierOutput:這個類別會初始化模型將輸出的類型。 模型輸出取決於模型定義的方式。 在我們的案例中,輸出將是 String 和 TensorFloat 類型為 map (dictionaries) 的序列,稱為 loss。

您現在會使用這些類別來載入、系結和評估專案中的模型。

載入模型和輸入

載入模型

  1. 按兩下程式 MainPage.xaml.cs 代碼檔案以開啟應用程式程式代碼。

  2. 將 「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;
  1. 在類別內的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 類別檔。 若要深入瞭解載入模型的其他方式,請檢閱 載入模型檔

  1. 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);
        }
  1. 現在,將對新方法的呼叫新增至 類別的建構函式。
        // 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);
        }

載入影像

  1. 我們需要定義 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();
        }
  1. 現在,您將實作 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 格式的檔案表示法。

  1. 將方法的實作 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 方法。

類似於 CreateFromStreamAsyncEvaluateAsync 此方法也由 WinML 程式代碼產生器自動產生,因此您不需要實作這個方法。 您可以在 檔案中 classifier.cs 檢閱此方法。

方法 EvaluateAsync 會使用已系結於系結的功能值,以異步方式評估機器學習模型。 它會使用 建立會話 LearningModelSession、使用 系結輸入和輸出 LearningModelBinding、執行模型評估,並使用 類別取得模型的 LearningModelEvaluationResult 輸出功能。

注意

若要了解執行模型的其他評估方法,請檢閱 LearningModelSession 類別文件,檢查哪些方法可以在 LearningModelSession 上實作。

  1. 將下列方法新增至 MainPage 類別內的程式 MainPage.xaml.cs 碼檔案,以建立工作階段、系結和評估模型。
        // A method to evaluate the model
        private async Task evaluate()
        {
            output = await modelGen.EvaluateAsync(input);
        }

擷取並顯示結果

您現在必須擷取模型輸出並顯示正確的結果。 您將藉由實 extractResult 作 和 displayResult 方法來執行此動作。

如您稍早探索,模型會傳回兩個輸出:第一個命名 classLabel 為字串的張量,而第二個具名損失則是一連串字串對浮點數對應,描述每個標記分類的機率。 因此,若要成功顯示結果和機率,我們只需要從遺失輸出擷取輸出。 我們需要找出最高的機率,才能傳回正確的結果。

  1. 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;
        }
  1. 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 程式代碼之後,您就可以測試應用程式。 確定頂端工具列中的下拉選單設定為 DebugSolution Platform x64如果您的裝置是64位,或 x86 32位,請將變更為 ,以在本機電腦上執行專案。

若要測試我們的應用程式,您將使用水果的下圖。 讓我們看看我們的 app 如何分類影像的內容。

Example fruit image

  1. 將此映像儲存在您的本機裝置上,以測試應用程式。 視需要將影像格式變更為 jpg。 您也可以以適當的格式從本機裝置新增任何其他相關影像 - .jpg、.png、.bmp或.gif格式。

  2. 若要執行專案,請按 Start Debugging 工具列上的按鈕,或按 F5

  3. 當應用程式啟動時,按 Pick Image 並選取本機裝置中的映像。

Pick image dialog

結果會立即出現在畫面上。 如您所見,我們的 WinML 應用程式已成功將影像分類為水果或蔬菜,信賴評等為 99.9%。

Successful image classification

摘要

您剛建立第一個 Windows 機器學習 應用程式,從模型建立到成功執行。

其他資源

若要深入了解此教學課程中提及的主題,請瀏覽下列資源: