教學課程:建立 Windows Machine Learning 傳統型應用程式 (C++)Tutorial: Create a Windows Machine Learning Desktop application (C++)
您可以利用 Windows ML API,輕鬆地與 C++ 傳統型應用程式 (Win32) 中的機器學習模型互動。Windows ML APIs can be leveraged to easily interact with machine learning models within C++ desktop (Win32) applications. 使用載入、繫結和評估的三個步驟,您的應用程式就能受益於機器學習的強大功能。Using the three steps of loading, binding, and evaluating, your application can benefit from the power of machine learning.
我們將會建立簡化版本的 SqueezeNet 物件偵測範例,可在 GitHub 上取得。We will be creating a somewhat simplified version of the SqueezeNet Object Detection sample, which is available on GitHub. 如果您想要在完成時查看其內容,您可以下載完整的範例。You can download the complete sample if you want to see what it will be like when you finish.
我們將使用 C++/WinRT 來存取 WinML API。We'll be using C++/WinRT to access the WinML APIs. 如需詳細資訊,請參閱 C++/WinRT。See C++/WinRT for more information.
在本教學課程中,您將了解如何:In this tutorial, you'll learn how to:
- 載入機器學習模型Load a machine learning model
- 以 VideoFrame 格式載入影像Load an image as a VideoFrame
- 繫結模型的輸入和輸出Bind the model's inputs and outputs
- 評估模型並列印有意義的結果Evaluate the model and print meaningful results
必要條件Prerequisites
- Visual Studio 2019 (或 Visual Studio 2017,15.7.4 版或更新版本)Visual Studio 2019 (or Visual Studio 2017, version 15.7.4 or later)
- Windows 10,版本 1809 或更新版本Windows 10, version 1809 or later
- Windows SDK,組建 17763 或更新版本Windows SDK, build 17763 or later
- 適用於 C++/WinRT 的 Visual Studio 擴充功能Visual Studio extension for C++/WinRT
- 在 Visual Studio 中,選取 [工具] > [擴充功能和更新] 。In Visual Studio, select Tools > Extensions and Updates.
- 在左窗格中選取 [Online] ,並使用右側的搜尋方塊搜尋 "WinRT"。Select Online in the left pane and search for "WinRT" using the search box on the right.
- 選取 [C++/WinRT] ,按一下 [下載] ,然後關閉 Visual Studio。Select C++/WinRT, click Download, and close Visual Studio.
- 依照安裝指示進行,然後重新開啟 Visual Studio。Follow the installation instructions, then re-open Visual Studio.
- Windows-Machine-Learning Github 存放庫 (您可以將其下載為 ZIP 檔案或複製到您的電腦)Windows-Machine-Learning Github repo (you can either download it as a ZIP file or clone to your machine)
建立專案Create the project
首先,我們會在 Visual Studio 中建立專案:First, we will create the project in Visual Studio:
- 選取 [檔案] > [新增] > [專案] 以開啟 [新增專案] 視窗。Select File > New > Project to open the New Project window.
- 在左窗格中,選取 [安裝] > [Visual C++] > [Windows 桌面] ,然後在中間選取 [Windows 主控台應用程式 (C++/WinRT)] 。In the left pane, select Installed > Visual C++ > Windows Desktop, and in the middle, select Windows Console Application (C++/WinRT).
- 為您的專案指定名稱和位置,然後按一下 [確定] 。Give your project a Name and Location, then click OK.
- 在 [新增通用 Windows 平台專案] 視窗中,將 [目標] 和 [最低版本] 都設定為組建 17763 或更新版本,然後按一下 [確定] 。In the New Universal Windows Platform Project window, set the Target and Minimum Versions both to build 17763 or later, and click OK.
- 請確定頂端工具列中的下拉式功能表已設定為 [偵錯] 和 x64 或 x86,視您電腦的架構而定。Make sure the dropdown menus in the top toolbar are set to Debug and either x64 or x86 depending on your computer's architecture.
- 按下 Ctrl+F5 以執行程式而不進行偵錯。Press Ctrl+F5 to run the program without debugging. 終端機應該會開啟並顯示一些 "Hello world" 文字。A terminal should open with some "Hello world" text. 按任意鍵將其關閉。Press any key to close it.
載入模型Load the model
接下來,我們會使用 LearningModel.LoadFromFilePath,將 ONNX 模型載入至我們的程式:Next, we'll load the ONNX model into our program using LearningModel.LoadFromFilePath:
在 pch.h (在標頭檔資料夾中),新增下列
include
陳述式 (可給予我們所需所有 API 的存取權):In pch.h (in the Header Files folder), add the followinginclude
statements (these give us access to all the APIs that we'll need):#include <winrt/Windows.AI.MachineLearning.h> #include <winrt/Windows.Foundation.Collections.h> #include <winrt/Windows.Graphics.Imaging.h> #include <winrt/Windows.Media.h> #include <winrt/Windows.Storage.h> #include <string> #include <fstream> #include <Windows.h>
在 main.cpp (在來源檔案資料夾中),新增下列
using
陳述式:In main.cpp (in the Source Files folder), add the followingusing
statements:using namespace Windows::AI::MachineLearning; using namespace Windows::Foundation::Collections; using namespace Windows::Graphics::Imaging; using namespace Windows::Media; using namespace Windows::Storage; using namespace std;
將下列變數宣告新增到
using
陳述式後面:Add the following variable declarations after theusing
statements:// Global variables hstring modelPath; string deviceName = "default"; hstring imagePath; LearningModel model = nullptr; LearningModelDeviceKind deviceKind = LearningModelDeviceKind::Default; LearningModelSession session = nullptr; LearningModelBinding binding = nullptr; VideoFrame imageFrame = nullptr; string labelsFilePath; vector<string> labels;
將下列向前宣告新增到全域變數後面:Add the following forward declarations after your global variables:
// Forward declarations void LoadModel(); VideoFrame LoadImageFile(hstring filePath); void BindModel(); void EvaluateModel(); void PrintResults(IVectorView<float> results); void LoadLabels();
在 main.cpp 中,移除 "Hello world" 程式碼 (
main
函式中init_apartment
後面的所有項目)。In main.cpp, remove the "Hello world" code (everything in themain
function afterinit_apartment
).在 Windows-Machine-Learning 存放庫的本機複本中,尋找 SqueezeNet.onnx 檔案。Find the SqueezeNet.onnx file in your local clone of the Windows-Machine-Learning repo. 其應該位於 \Windows-Machine-Learning\SharedContent\models 中。It should be located in \Windows-Machine-Learning\SharedContent\models.
複製檔案路徑,並將其指派給我們在頂端定義的
modelPath
變數。Copy the file path and assign it to yourmodelPath
variable where we defined it at the top. 請記得在字串前面加上L
,使其成為寬字元字串,以便適當地搭配hstring
使用,以及使用額外的反斜線 (\
) 來將任何反斜線逸出。Remember to prefix the string with anL
to make it a wide character string so that it works properly withhstring
, and to escape any backslashes (\
) with an extra backslash. 例如:For example:hstring modelPath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\models\\SqueezeNet.onnx";
首先,我們將會實作
LoadModel
方法。First, we'll implement theLoadModel
method. 將下列方法新增到main
方法後面。Add the following method after themain
method. 這個方法會載入模型,並輸出其所花費的時間:This method loads the model and outputs how long it took:void LoadModel() { // load the model printf("Loading modelfile '%ws' on the '%s' device\n", modelPath.c_str(), deviceName.c_str()); DWORD ticks = GetTickCount(); model = LearningModel::LoadFromFilePath(modelPath); ticks = GetTickCount() - ticks; printf("model file loaded in %d ticks\n", ticks); }
最後,從
main
方法呼叫這個方法:Finally, call this method from themain
method:LoadModel();
在不進行偵錯的情況下執行程式。Run your program without debugging. 您應該會看到您的模型載入成功!You should see that your model loads successfully!
載入影像Load the image
接下來,我們會將影像檔案載入至程式:Next, we'll load the image file into our program:
新增下列方法。Add the following method. 這個方法會從指定的路徑載入影像,從其建立一個 VideoFrame:This method will load the image from the given path and create a VideoFrame from it:
VideoFrame LoadImageFile(hstring filePath) { printf("Loading the image...\n"); DWORD ticks = GetTickCount(); VideoFrame inputImage = nullptr; try { // open the file StorageFile file = StorageFile::GetFileFromPathAsync(filePath).get(); // get a stream on it auto stream = file.OpenAsync(FileAccessMode::Read).get(); // Create the decoder from the stream BitmapDecoder decoder = BitmapDecoder::CreateAsync(stream).get(); // get the bitmap SoftwareBitmap softwareBitmap = decoder.GetSoftwareBitmapAsync().get(); // load a videoframe from it inputImage = VideoFrame::CreateWithSoftwareBitmap(softwareBitmap); } catch (...) { printf("failed to load the image file, make sure you are using fully qualified paths\r\n"); exit(EXIT_FAILURE); } ticks = GetTickCount() - ticks; printf("image file loaded in %d ticks\n", ticks); // all done return inputImage; }
在
main
方法中,新增對此方法的呼叫:Add a call to this method in themain
method:imageFrame = LoadImageFile(imagePath);
在 Windows-Machine-Learning 存放庫的本機複本中,尋找媒體資料夾。Find the media folder in your local clone of the Windows-Machine-Learning repo. 其應該位於 \Windows-Machine-Learning\SharedContent\media 中。It should be located at \Windows-Machine-Learning\SharedContent\media.
選擇該資料夾中的其中一個影像,並將其檔案路徑指派給我們在頂端定義的
imagePath
變數。Choose one of the images in that folder, and assign its file path to theimagePath
variable where we defined it at the top. 請記得在前面加上L
,使其成為寬字元字串,並使用其他反斜線來將任何反斜線逸出。Remember to prefix it with anL
to make it a wide character string, and to escape any backslashes with another backslash. 例如:For example:hstring imagePath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\media\\kitten_224.png";
在不進行偵錯的情況下執行程式。Run the program without debugging. 您應該會看到已成功載入影像!You should see the image loaded successfully!
繫結輸入和輸出Bind the input and output
接下來,我們會根據模型建立工作階段,並使用 LearningModelBinding.Bind 來繫結工作階段的輸入和輸出。Next, we'll create a session based on the model and bind the input and output from the session using LearningModelBinding.Bind. 如需繫結的詳細資訊,請參閱繫結模式。For more information on binding, see Bind a model.
實作
BindModel
方法。Implement theBindModel
method. 這樣會根據模型和裝置,以及以該工作階段為基礎的繫結來建立工作階段。This creates a session based on the model and device, and a binding based on that session. 接著,我們會將輸入和輸出繫結至我們使用其名稱建立的變數。We then bind the inputs and outputs to variables we've created using their names. 我們事先知道輸入功能的名稱是 "data_0",而輸出功能的名稱是 "softmaxout_1"。We happen to know ahead of time that the input feature is named "data_0" and the output feature is named "softmaxout_1". 您可以在 Netron (線上模型視覺效果工具) 中開啟任何模型的這些屬性,來查看這些屬性。You can see these properties for any model by opening them in Netron, an online model visualization tool.void BindModel() { printf("Binding the model...\n"); DWORD ticks = GetTickCount(); // now create a session and binding session = LearningModelSession{ model, LearningModelDevice(deviceKind) }; binding = LearningModelBinding{ session }; // bind the intput image binding.Bind(L"data_0", ImageFeatureValue::CreateFromVideoFrame(imageFrame)); // bind the output vector<int64_t> shape({ 1, 1000, 1, 1 }); binding.Bind(L"softmaxout_1", TensorFloat::Create(shape)); ticks = GetTickCount() - ticks; printf("Model bound in %d ticks\n", ticks); }
從
main
方法新增對BindModel
的呼叫:Add a call toBindModel
from themain
method:BindModel();
在不進行偵錯的情況下執行程式。Run the program without debugging. 應該成功繫結模型的輸入和輸出。The model's inputs and outputs should be bound successfully. 即將完成!We're almost there!
評估模型Evaluate the model
我們現在來到本教學課程開頭圖表中的最後一個步驟,評估。We're now on the last step in the diagram at the beginning of this tutorial, Evaluate. 我們將會使用 LearningModelSession.Evaluate 來評估模型:We'll evaluate the model using LearningModelSession.Evaluate:
實作
EvaluateModel
方法。Implement theEvaluateModel
method. 這個方法會採用我們的工作階段,並使用我們的繫結和相互關聯識別碼來進行評估。This method takes our session and evaluates it using our binding and a correlation ID. 相互關聯識別碼是我們稍後可能會用來比對特定評估呼叫與輸出結果的項目。The correlation ID is something we could possibly use later to match a particular evaluation call to the output results. 同樣地,我們事先知道輸出的名稱是 "softmaxout_1"。Again, we know ahead of time that the output's name is "softmaxout_1".void EvaluateModel() { // now run the model printf("Running the model...\n"); DWORD ticks = GetTickCount(); auto results = session.Evaluate(binding, L"RunId"); ticks = GetTickCount() - ticks; printf("model run took %d ticks\n", ticks); // get the output auto resultTensor = results.Outputs().Lookup(L"softmaxout_1").as<TensorFloat>(); auto resultVector = resultTensor.GetAsVectorView(); PrintResults(resultVector); }
現在讓我們來實作
PrintResults
。Now let's implementPrintResults
. 這個方法會取得影像中可能物件的前三名,並加以列印:This method gets the top three probabilities for what object could be in the image, and prints them:void PrintResults(IVectorView<float> results) { // load the labels LoadLabels(); // Find the top 3 probabilities vector<float> topProbabilities(3); vector<int> topProbabilityLabelIndexes(3); // SqueezeNet returns a list of 1000 options, with probabilities for each, loop through all for (uint32_t i = 0; i < results.Size(); i++) { // is it one of the top 3? for (int j = 0; j < 3; j++) { if (results.GetAt(i) > topProbabilities[j]) { topProbabilityLabelIndexes[j] = i; topProbabilities[j] = results.GetAt(i); break; } } } // Display the result for (int i = 0; i < 3; i++) { printf("%s with confidence of %f\n", labels[topProbabilityLabelIndexes[i]].c_str(), topProbabilities[i]); } }
我們也需要實作
LoadLabels
。We also need to implementLoadLabels
. 這個方法會開啟標籤檔案,其中包含模型可以辨識的所有不同物件,並加以剖析:This method opens the labels file that contains all of the different objects that the model can recognize, and parses it:void LoadLabels() { // Parse labels from labels file. We know the file's entries are already sorted in order. ifstream labelFile{ labelsFilePath, ifstream::in }; if (labelFile.fail()) { printf("failed to load the %s file. Make sure it exists in the same folder as the app\r\n", labelsFilePath.c_str()); exit(EXIT_FAILURE); } std::string s; while (std::getline(labelFile, s, ',')) { int labelValue = atoi(s.c_str()); if (labelValue >= labels.size()) { labels.resize(labelValue + 1); } std::getline(labelFile, s); labels[labelValue] = s; } }
在 Windows-Machine-Learning 存放庫的本機複本中,尋找 Labels.txt 檔案。Locate the Labels.txt file in your local clone of the Windows-Machine-Learning repo. 其應該位於 \Windows-Machine-Learning\Samples\SqueezeNetObjectDetection\Desktop\cpp 中。It should be in \Windows-Machine-Learning\Samples\SqueezeNetObjectDetection\Desktop\cpp.
將此檔案路徑指派給我們在頂端定義的
labelsFilePath
變數。Assign this file path to thelabelsFilePath
variable where we defined it at the top. 請務必使用另一個反斜線來將任何反斜線逸出。Make sure to escape any backslashes with another backslash. 例如:For example:string labelsFilePath = "C:\\Repos\\Windows-Machine-Learning\\Samples\\SqueezeNetObjectDetection\\Desktop\\cpp\\Labels.txt";
在
main
方法中新增對EvaluateModel
的呼叫:Add a call toEvaluateModel
in themain
method:EvaluateModel();
在不進行偵錯的情況下執行程式。Run the program without debugging. 它現在應該會正確辨識影像中的內容!It should now correctly recognize what's in the image! 以下是其可能輸出範例:Here is an example of what it might output:
Loading modelfile 'C:\Repos\Windows-Machine-Learning\SharedContent\models\SqueezeNet.onnx' on the 'default' device model file loaded in 250 ticks Loading the image... image file loaded in 78 ticks Binding the model...Model bound in 15 ticks Running the model... model run took 16 ticks tabby, tabby cat with confidence of 0.931461 Egyptian cat with confidence of 0.065307 Persian cat with confidence of 0.000193
接下來的步驟Next steps
太棒了,您可以在 C++ 傳統型應用程式中使用物件偵測!Hooray, you've got object detection working in a C++ desktop application! 接下來,您可以嘗試使用命令列引數來輸入模型和影像檔案,而不是針對其進行硬式編碼,類似於 GitHub 上的範例。Next, you can try using command line arguments to input the model and image files rather than hardcoding them, similar to what the sample on GitHub does. 您也可以嘗試在不同的裝置 (例如 GPU) 上執行評估,以查看效能有何差異。You could also try running the evaluation on a different device, like the GPU, to see how the performance differs.
使用 GitHub 上的其他範例,以您喜愛的方式將其延伸!Play around with the other samples on GitHub and extend them however you like!
另請參閱See also
- Windows ML 範例 (GitHub)Windows ML samples (GitHub)
- Windows.AI.MachineLearning 命名空間Windows.AI.MachineLearning Namespace
- Windows MLWindows ML
注意
使用下列資源取得 Windows ML 的說明:Use the following resources for help with Windows ML:
- 如需詢問或回答有關 Windows ML 的技術問題,請使用 Stack Overflow 上的 windows-machine-learning 標籤。To ask or answer technical questions about Windows ML, please use the windows-machine-learning tag on Stack Overflow.
- 如需回報錯誤 (bug),請在 GitHub 上提出問題。To report a bug, please file an issue on our GitHub.
- 如需要求功能,請前往 Windows 開發人員意見反應。To request a feature, please head over to Windows Developer Feedback.