WinML アドオンの作成

このガイドでは、Electron アプリで Windows Machine Learning (WinML) を使用する C# ネイティブ アドオンを作成する方法について説明します。 WinML を使用すると、画像分類、物体検出などのタスクのために、Windows デバイス上で machine learning モデル (ONNX 形式) をローカルで実行できます。

[前提条件]

このガイドを開始する前に、次の作業が完了していることを確認してください。

  • 開発環境のセットアップが完了しました
  • Windows 11 またはWindows 10 (バージョン 1809 以降)

WinML は、任意のWindows 10 (1809 以降) またはWindows 11デバイスで実行されます。 パフォーマンスを最大限に高めるには、GPU または NPU を搭載したデバイスをお勧めしますが、API は CPU でも動作します。

Important

WinML アドオンには、experimental Windows アプリ SDKが必要です。 セットアップ ガイドの winapp init 中に [Stable SDK] を選択した場合は、SDK のバージョンを更新する必要があります。 winapp.yaml を編集し、Microsoft.WindowsAppSDK バージョンを 2.0.0-experimental3 に変更してから、npx winapp restore を実行して更新します。

手順 1: C# ネイティブ アドオンを作成する

WinML API を使用するネイティブ アドオンを作成してみましょう。 node-api-dotnet を利用して JavaScript と C# をブリッジする C# テンプレートを使用します。

npx winapp node create-addon --template cs --name winMlAddon

これにより、次の winMlAddon/ フォルダーが作成されます。

  • addon.cs - WinML API を呼び出す C# コード
  • winMlAddon.csproj - Windows SDK と Windows アプリ SDK への参照を含むProject ファイル
  • README.md - アドオンの使用方法に関するドキュメント

また、このコマンドは、アドオンをビルドするためのbuild-winMlAddon スクリプトと、ビルド成果物をクリーニングするためのpackage.json スクリプトをclean-winMlAddonに追加します。

{
  "scripts": {
    "build-winMlAddon": "dotnet publish ./winMlAddon/winMlAddon.csproj -c Release",
    "clean-winMlAddon": "dotnet clean ./winMlAddon/winMlAddon.csproj"
  }
}

テンプレートには両方の SDK への参照が自動的に含まれるため、Windows API の呼び出しをすぐに開始できます。

アドオンをビルドして、すべてが正しく設定されていることを確認しましょう。

# Build the C# addon
npm run build-winMlAddon

npx winapp node create-addon (--template フラグなし) を使用して C++ アドオンを作成することもできます。 C++ アドオンは、node-addon-api を使用し、最大のパフォーマンスでWindows API に直接アクセスできるようにします。 その他のオプションについては、チュートリアルまたは完全なコマンド ドキュメントについては、C++ 通知アドオン ガイドを参照してください。

手順 2: SqueezeNet モデルをダウンロードしてサンプル コードを取得する

参照として、AI 開発ギャラリー画像分類サンプルを使用します。 このサンプルでは、画像分類に SqueezeNet 1.1 モデルを使用します。

2.1。 モデルをダウンロードする

  1. AI 開発ギャラリーをインストールする
  2. 画像の分類のサンプルに移動する
  3. SqueezeNet 1.1 モデルをダウンロードする (CPU、GPU、NPU をサポート)
  4. [ フォルダーを含むファイルを開く ] をクリックして、 .onnx ファイルを見つけます

AI Dev Gallery からの SqueezeNet のダウンロード

  1. project ルートの squeezenet1.1.onnx フォルダーに models/ ファイルをコピーします

手順 3: 必要な NuGet パッケージを追加する

WinML コードを追加する前に、画像処理、ONNX ランタイム、GenAI のサポートに必要な NuGet パッケージを追加する必要があります。

3.1。 Directory.packages.props の更新

次のパッケージ バージョンをプロジェクトのルートの Directory.packages.props ファイルに追加します (アドオンの作成時に作成されている必要があります)。

<Project>
  <PropertyGroup>
    <!-- Enable central package versioning -->
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="Microsoft.JavaScript.NodeApi" Version="0.9.17" />
    <PackageVersion Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.9.17" />
    <!-- Add these packages for WinML -->
+   <PackageVersion Include="Microsoft.ML.OnnxRuntime.Extensions" Version="0.14.0" />
+   <PackageVersion Include="System.Drawing.Common" Version="9.0.9" />
+   <PackageVersion Include="Microsoft.Extensions.AI" Version="9.9.1" />
+   <PackageVersion Include="Microsoft.ML.OnnxRuntimeGenAI.Managed" Version="0.10.1" />
+   <PackageVersion Include="Microsoft.ML.OnnxRuntimeGenAI.WinML" Version="0.10.1" />
    
    <!-- These versions may be updated automatically during restore to match yaml -->
    <PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.0.0-experimental3" />
    <PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />
  </ItemGroup>
</Project>

3.2. winMlAddon.csproj を更新する

winMlAddon/winMlAddon.csprojを開き、パッケージ参照を<ItemGroup>に追加します。

<ItemGroup>
  <PackageReference Include="Microsoft.JavaScript.NodeApi" />
  <PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" />
  <!-- Add these packages for WinML -->
+ <PackageReference Include="Microsoft.ML.OnnxRuntime.Extensions" />
+ <PackageReference Include="System.Drawing.Common" />
+ <PackageReference Include="Microsoft.Extensions.AI" />
+ <PackageReference Include="Microsoft.ML.OnnxRuntimeGenAI.Managed" />
+ <PackageReference Include="Microsoft.ML.OnnxRuntimeGenAI.WinML" />
  
  <PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
  <PackageReference Include="Microsoft.WindowsAppSDK" />
</ItemGroup>

これらのパッケージの実行内容:

  • Microsoft.ML.OnnxRuntime.Extensions - ONNX ランタイムの追加の演算子とユーティリティを提供します
  • System.Drawing.Common - 画像の読み込みと前処理の操作を有効にします
  • Microsoft。Extensions.AI - .NETのための AI 抽象化
  • Microsoft.ML.OnnxRuntimeGenAI.Managed - ONNX ランタイム GenAI のマネージド バインド
  • Microsoft.ML.OnnxRuntimeGenAI.WinML - ONNX Runtime GenAI の WinML 統合

手順 4: サンプル コードを追加する

AI 開発ギャラリーには、SqueezeNet を使用した画像分類の完全な実装が示されています。

SqueezeNet サンプル コード

このコードは Electron 用に調整されており、 electron-winml サンプルで完全な実装を見つけることができます。 winMlAddon/ フォルダーには、AI 開発ギャラリーから変更されたコードが含まれています。

winMlAddon/ フォルダー全体を samples/electron-winml/winMlAddon/ からプロジェクト ルートにコピーし、手順 1 で作成したフォルダーを置き換えます。 このサンプルには、アドオンのビルドと実行に必要な addon.cs ( Utils/ のヘルパー クラス、チャット クライアントなど) 以外の複数のファイルが含まれています。

Important

だけでなく、addon.csをコピーする必要があります。 アドオンは、 Utils/ サブフォルダー (Prediction.csImageNet.csBitmapFunctions.csなど) のヘルパー ファイルに依存します。

主要な実装の詳細

実装の重要な部分と、AI Dev Gallery コードとの主な違いを強調しましょう。

プロジェクト ルートパス要件

AI Dev Gallery コードとは異なり、Electron アドオンでは 、プロジェクトのルート パスを渡すために JavaScript コードが必要です。 これは次の理由で必要です。

  • アドオンは、 models/ フォルダー内の ONNX モデル ファイルを見つける必要があります
  • ネイティブ依存関係 (DLL) を特定のディレクトリから読み込む必要がある
[JSExport]
public static async Task<Addon> CreateAsync(string projectRoot)
{
    if (!Path.Exists(projectRoot))
    {
        throw new Exception("Project root is invalid.");
    }

    var addon = new Addon(projectRoot);
    addon.PreloadNativeDependencies();

    string modelPath = Path.Join(projectRoot, "models", @"squeezenet1.1-7.onnx");
    await addon.InitModel(modelPath, ExecutionProviderDevicePolicy.DEFAULT, null, false, null);

    return addon;
}

これにより、デバイスの機能に基づいて最適な実行プロバイダー (CPU、GPU、または NPU) が自動的に選択されます。

2. ネイティブ依存関係の事前読み込み

アドオンには、必要な DLL を読み込む PreloadNativeDependencies() メソッドが含まれています。 このアプローチは、DLL をプロジェクト ルートにコピーする必要なく、 開発と運用 の両方のシナリオで機能します。

private void PreloadNativeDependencies()
{
    // Loads required DLLs from the winMlAddon build output
    // This ensures dependencies are available regardless of the execution context
}

これは、モデルを読み込む前に初期化中に呼び出され、すべてのネイティブ ライブラリが使用可能であることを確認します。

3. パッケージ用電子フォージの構成

実稼働ビルドでアドオンが正しく動作することを確認するには、パッケージャーを次のように構成する必要があります。

  1. ネイティブ ファイルのアンパック - DLL、ONNX モデル、および .node ファイルに ASAR アーカイブの外部からアクセスできる必要があります
  2. 不要なファイルを除外 する - ビルド成果物と一時ファイルを除外してパッケージ サイズを小さくする

Electron Forge の場合は、forge.config.jsを更新します。

// From samples/electron-winml/forge.config.js
module.exports = {
  packagerConfig: {
    asar: {
      // Unpack native files so they can be accessed by the addon
      unpack: "**/*.{dll,exe,node,onnx}"
    },
    ignore: [
      // Exclude .winapp folder (SDK packages and headers)
      /^\/.winapp\//,
      // Exclude MSIX packages
      "\\.msix$",
      // Exclude winMlAddon source files, but keep the dist folder
      /^\/winMlAddon\/(?!dist).+/
    ]
  },
  // ... rest of your config
};

機能:

  1. asar.unpack - DLL、実行可能ファイル、.node バイナリ、ONNX モデルを抽出して、 app.asar.unpacked/

    • これにより、実行時にファイル システム パスを介してアクセスできるようになります
    • JavaScript コードでは、パスが自動的に調整されます (上記の app.asarapp.asar.unpacked の置き換えを参照してください)
  2. ignore - 最終的なパッケージから除外されます。

    • .winapp/ - SDK パッケージとヘッダー (実行時には必要ありません)
    • .msix files - パッケージ化された出力
    • winMlAddon/ ソース ファイル - コンパイル済みバイナリを含む dist/ フォルダーのみを保持します

別のパッケージ化ツール (electron-builder など) を使用している場合は、ネイティブの依存関係をアンパックし、開発ファイルを除外するために同様の設定を構成する必要があります。 ASAR アンパック オプションについては、パッケージャーのドキュメントを参照してください。

4. 画像分類

ClassifyImage メソッドは画像を処理し、予測を返します。

[JSExport]
public async Task<Prediction[]> ClassifyImage(string imagePath)
{
    // Loads the image, preprocesses it, and runs inference
    // Returns top predictions with labels and confidence scores
}

完全な実装は次を管理します:

  • 画像の読み込みと前処理 (サイズ変更、正規化)
  • モデル推論の実行
  • ラベルと信頼度スコアを使用して上位の予測を取得する後処理結果

完全なソース コードには、イメージの前処理、テンソルの作成、結果の解析が含まれます。 すべての詳細については、 サンプル実装 を確認してください。

コードについて理解する

アドオンは、次の主な機能を提供します。

  1. CreateAsync - アドオンを初期化し、SqueezeNet モデルを読み込みます
  2. ClassifyImage - 画像パスを取得し、分類予測を返します

WinML は、可用性に基づいて最適な実行デバイス (CPU、GPU、または NPU) を自動的に選択します。

手順 5: C# アドオンをビルドする

次に、アドオンをビルドします。

npm run build-winMlAddon

これにより、 ネイティブ AOT (Ahead-of-Time コンパイル) を使用して C# コードがコンパイルされます。これは次のとおりです。

  • .node バイナリ (ネイティブ アドオン形式) を作成します。
  • 未使用のコードをトリミングしてバンドル サイズを小さくする
  • ターゲット マシンに.NETランタイムは必要ありません。
  • ネイティブ パフォーマンスを提供します

コンパイルされたアドオンは winMlAddon/dist/winMlAddon.node

手順 6: アドオンをテストする

次に、メイン プロセスからアドオンを呼び出して、アドオンの動作をテストしてみましょう。 src/main.jsを開き、次の手順に従います。

6.1。 アドオンを読み込む

先頭に require ステートメントを追加します。

const winMlAddon = require('../winMlAddon/dist/winMlAddon.node');

6.2. テスト関数を作成する

画像分類をテストするには、次の関数を追加します。

const testWinML = async () => {
  console.log('Testing WinML addon...');
  
  try {
    let projectRoot = path.join(__dirname, '..');
    // Adjust path for packaged apps
    if (projectRoot.includes('app.asar')) {
      projectRoot = projectRoot.replace('app.asar', 'app.asar.unpacked');
    }
    
    const addon = await winMlAddon.Addon.createAsync(projectRoot);
    console.log('Model loaded successfully!');
    
    // Classify a sample image
    const imagePath = path.join(projectRoot, 'test-images', 'sample.jpg');
    const predictions = await addon.classifyImage(imagePath);
    
    console.log('Top predictions:');
    predictions.slice(0, 5).forEach((pred, i) => {
      console.log(`${i + 1}. ${pred.label}: ${(pred.confidence * 100).toFixed(2)}%`);
    });
  } catch (error) {
    console.error('Error testing WinML:', error.message);
  }
};

重要なポイント:

  • パス調整 (app.asarapp.asar.unpacked) により、開発アプリとパッケージ アプリの両方でコードが動作します
  • これにより、 で構成されたアンパックされたネイティブファイルにアクセスします。

6.3. Test 関数を呼び出す

createWindow()関数の末尾に次の行を追加します。

testWinML();

6.4. テスト イメージを準備する

画像分類をテストするには:

  1. プロジェクト ルートに test-images/ フォルダーを作成する
  2. sample.jpgという名前のテスト イメージを追加します (コードには、この正確なファイル名が必要です)
  3. SqueezeNet モデルは、1,000 種類の ImageNet クラス (動物、オブジェクト、シーンなど) を認識します。

アプリを実行すると、コンソールに分類結果が表示されます。

Tip

IPC ハンドラー、ファイル選択ダイアログ、UI を使用した完全な実装については、 electron-winml サンプルを参照してください。

手順 7: デバッグ ID を更新する

Windows アプリ SDKが読み込まれ、使用できるようにするには、アプリの実行時にフレームワークが確実に読み込まれるようにデバッグ ID を設定する必要があります。 同様に、マニフェストで参照されている Package.appxmanifest を変更したり、アセットを変更したりするたびに (アプリ アイコンなど)、アプリのデバッグ ID を更新する必要があります。 走れ

npx winapp node add-electron-debug-identity

このコマンド:

  1. Package.appxmanifestを読み取り、アプリの詳細と機能を取得します
  2. electron.exe内でnode_modulesを一時的な ID で登録します
  3. 完全な MSIX パッケージ化なしで ID が必要な API をテストできます

このコマンドは既にセットアップ ガイドで追加した postinstall スクリプトの一部であるため、 npm install後に自動的に実行されます。 ただし、次の場合は必ず手動で実行する必要があります。

  • Package.appxmanifestの変更 (機能、ID、またはプロパティの変更)
  • アプリ資産 (アイコン、ロゴなど) を更新する

次に、アプリを実行します。

npm start

コンソールの出力を確認します。WinML テスト結果が表示されます。

⚠️ 既知の問題: アプリのクラッシュまたは空のウィンドウ (クリックして展開)

電子アプリケーションのパッケージ化が疎である既知のWindowsバグがあり、アプリが起動時にクラッシュしたり、Web コンテンツをレンダリングしたりすることはありません。 この問題はWindowsで修正されましたが、まだすべてのデバイスに反映されていません。

回避策については、 開発環境のセットアップ を参照してください。

次のステップ

おめでとうございます! WinML で機械学習モデルを実行できるネイティブ アドオンが正常に作成されました。 🎉

これで、次の準備ができました。

または、他のガイドを調べる:

あなたのモデル向けのカスタマイズ

ONNX モデルを完全に統合するには、次の手順を実行する必要があります。

  1. モデルの入力 ( 画像、テンソル、シーケンスなど) を理解します。
  2. 適切な入力バインドを作成する - データを WinML で想定される形式に変換する
  3. 出力を処理する - モデルの予測を解析して解釈する
  4. エラーを適切に処理 する - モデルの読み込みと推論が失敗する可能性があります

その他のリソース

Troubleshooting

NU1010 でビルドが失敗する: PackageReference 項目で対応する PackageVersion が定義されない

winMlAddon.csprojで参照されているすべてのパッケージが、Directory.packages.propsに一致するエントリを持っていることを確認します。 必要なパッケージの完全な一覧については、手順 3 を参照してください。

アドオンを読み込むときに "有効な Win32 アプリケーションではありません"

つまり、アドオンは、Node.js/Electron ランタイムとは異なるアーキテクチャ用に構築されています。 Node.js アーキテクチャを確認します。

node -e "console.log(process.arch)"

次に、一致するターゲットでアドオンを再構築します。

# For x64 Node.js:
dotnet publish ./winMlAddon/winMlAddon.csproj -c Release -r win-x64

# For ARM64 Node.js:
dotnet publish ./winMlAddon/winMlAddon.csproj -c Release -r win-arm64

Node.js のインストールを最近変更した場合は、 node_modules を再インストールして、一致する Electron バイナリを取得します。

rm -rf node_modules package-lock.json
npm install

ヘルプを取得する

幸せな機械学習! 🤖