Windows ML のモデルを配布する

Windows ML はローカル ファイル パスからモデルを読み込むため、特定の配布方法は必要ありません。 アプリは、パッケージの一部としてモデルを出荷するか、モデルを個別に取得します。インストーラーでダウンロードするか、初めて必要なときにアプリによって取得します。 この記事では、2 つのアプローチを比較し、それぞれを実装する手順について説明します。

アプローチを選択する

パッケージにモデルを含める モデルを個別にダウンロードする
最適な用途 小規模なモデル、またはインストール直後にオフラインで動作する必要があるモデル 大規模なモデル、またはアプリとは別に配布するモデル
初回実行時エクスペリエンス モデルはすぐに利用できます インストーラーでダウンロードするか、バックグラウンドでプリフェッチしない限り、最初に使用する前にダウンロードが必要です
アプリ サイズへの影響 すべてのユーザーのインストール 済みサイズを増やします ユーザーがモデルを必要とする機能をオプトインするデバイスでのみストレージを使用します
オフライン サポート 常にオフラインで使用可能 最初のダウンロードが成功した後にのみオフラインで使用できます

モデルが数メガバイトでほとんど変更されない場合は、バンドルする方が簡単で、ユーザーにすぐに作業アプリが提供されます。 モデルのサイズが大きい場合、頻繁に更新される場合、または一部のユーザーに対してオプションの場合は、代わりに個別にダウンロードしてください。

Tip

複数の独立したアプリが依存するライブラリまたは SDK を発行していて、それらのアプリでモデルの 1 つのディスク上のコピーを共有する必要がある場合は、独自のダウンロードおよび共有ロジックを記述する代わりに、Windows ML モデル カタログ API を使用します。 独自のアプリのモデルのみを必要とするほとんどのアプリ開発者は、ここで説明する 2 つの方法のいずれかを使用する必要があります。

パッケージにモデルを含める

ONNX モデル ファイルを、トークナイザーやラベル ファイルなどの依存ファイルと共に、アプリ プロジェクトのコンテンツとして追加します。 ML Windows、ネットワーク アクセスを必要とせず、アプリのインストール場所からモデルを直接読み込みます。

実装の手順

  1. モデル ファイルをプロジェクトに追加し、 ビルド アクション を コンテンツ (またはプロジェクトの種類に相当) に設定して、出力にコピーしてアプリ パッケージに含めます。
  2. 実行時にアプリのインストール場所を基準にして、モデルのパスを解決します。 インストール場所はユーザーと展開の種類によって異なるため、絶対パスをハードコーディングしないでください。
  3. 推論 API を使用して、そのパスからモデルを読み込みます。
using System;
using System.IO;

// Resolve the model path relative to the app's install location.
string modelPath = Path.Combine(AppContext.BaseDirectory, "Assets", "Models", "model.onnx");

if (!File.Exists(modelPath))
{
    throw new FileNotFoundException("The bundled model is missing from the app package.", modelPath);
}

// Load modelPath with your inference APIs.

考慮事項

  • パッケージ サイズ: すべてのインストールには完全なモデルが搭載されており、この機能を使用しない可能性があるユーザーを含め、すべてのユーザーのダウンロード サイズとディスク使用量が増加します。
  • ダウンロードエラーの処理は必要ありません:アプリの起動時にモデルは既にディスク上にあるため、再試行、整合性チェック、オフライン フォールバック ロジックは必要ありません。
  • オプション パッケージ: モデルを基本インストールから除外するが、ダウンロード ロジックの記述を避けたい場合は、ユーザーまたはアプリがオンデマンドでインストールするオプションの MSIX パッケージで出荷できます。 これにより、単純なダウンロードではなくパッケージの展開に依存する代わりに、カスタム ダウンロード パスを回避しながら、基本パッケージを小さく保ちます。

モデルを個別にダウンロードする

モデルをバンドルする代わりに、アプリ パッケージとは別にデバイスにダウンロードし、アプリローカル ストレージに格納します。 これにより、パッケージが小さくなります。 ダウンロードは実行中のアプリから行う必要はありません。最初の実行時にアプリからモデルをダウンロードするか、セットアップの一部としてインストーラーにダウンロードしてもらうことができます。 いずれの場合も、ファイルのダウンロード、検証、保存、クリーンアップを行う必要があります。

実装の手順

  1. ダウンロードを確認できるように、モデル ファイル (および依存ファイル) を HTTPS URL と SHA-256 ハッシュでホストします。
  2. ダウンロードがいつ行われるかを決定します。インストーラーで行うか、初めてモデルが必要になったときにアプリからダウンロードするかを決定します。 どちらの方法でも、同じダウンロードおよび検証ロジックが使用されます。
  3. モデルがローカル ストレージに既に存在するかどうかを確認してから、もう一度ダウンロードしてください。
  4. ファイルを書き込み可能なアプリ ローカル フォルダーに保存し、使用する前にそのハッシュを確認します。
  5. ダウンロードの進行状況をユーザーに報告し、ネットワーク障害を処理して、ダウンロードを再試行または正常にフォールバックできるようにします。
using System;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;

public static async Task<string> GetModelPathAsync(
    Uri modelUri,
    string expectedSha256,
    IProgress<double>? progress = null,
    CancellationToken cancellationToken = default)
{
    string modelsFolder = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
        "Contoso", "Models");
    Directory.CreateDirectory(modelsFolder);

    string modelPath = Path.Combine(modelsFolder, Path.GetFileName(modelUri.LocalPath));

    // Reuse the file if it's already downloaded and passes integrity verification.
    if (File.Exists(modelPath) && await VerifyHashAsync(modelPath, expectedSha256, cancellationToken))
    {
        return modelPath;
    }

    string tempPath = modelPath + ".download";

    using (var httpClient = new HttpClient())
    using (var response = await httpClient.GetAsync(
        modelUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
    {
        response.EnsureSuccessStatusCode();

        long? totalBytes = response.Content.Headers.ContentLength;
        long bytesRead = 0;

        using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken);
        using var fileStream = File.Create(tempPath);

        var buffer = new byte[81920];
        int read;
        while ((read = await contentStream.ReadAsync(buffer, cancellationToken)) > 0)
        {
            await fileStream.WriteAsync(buffer.AsMemory(0, read), cancellationToken);
            bytesRead += read;

            if (totalBytes.HasValue)
            {
                progress?.Report((double)bytesRead / totalBytes.Value);
            }
        }
    }

    if (!await VerifyHashAsync(tempPath, expectedSha256, cancellationToken))
    {
        File.Delete(tempPath);
        throw new InvalidOperationException("Downloaded model failed integrity verification.");
    }

    File.Move(tempPath, modelPath, overwrite: true);
    return modelPath;
}

private static async Task<bool> VerifyHashAsync(
    string filePath, string expectedSha256, CancellationToken cancellationToken)
{
    using var sha256 = SHA256.Create();
    using var fileStream = File.OpenRead(filePath);
    byte[] hash = await sha256.ComputeHashAsync(fileStream, cancellationToken);
    string actualSha256 = Convert.ToHexString(hash);
    return string.Equals(actualSha256, expectedSha256, StringComparison.OrdinalIgnoreCase);
}

考慮事項

  • 整合性の検証: モデルを使用するか、キャッシュとして扱う前に、ダウンロード後に必ず SHA-256 ハッシュ (または類似) を確認してください。 そうしないと、部分的または破損したダウンロードが推論時に自動的に失敗する可能性があります。
  • アトミック書き込み: 一時ファイルにダウンロードし、検証が成功した後にのみ名前を変更します。そのため、失敗または中断されたダウンロードによって、破損したファイルが予期したパスに残ることはありません。
  • 再試行とオフライン処理: ネットワーク エラーを明示的に処理します。 アプリを機能制限モードで実行できるかどうか、ユーザーに再試行を求めるメッセージを表示するか、ダウンロードが成功するまで機能をブロックするかを決定します。
  • ストレージの場所: アプリの更新後も存続するが、アプリがアンインストールされるとクリーンアップされる、アプリローカルの書き込み可能なフォルダーを使用します。
  • クリーンアップ: モデルを新しいバージョンに置き換える場合は、ユーザーのデバイスに未使用のファイルが蓄積されないように、古いファイルを削除します。
  • インストーラーでのダウンロードと最初の実行: インストーラーがカスタム アクションまたはスクリプトの実行をサポートしている場合は、インストールの一環としてモデルをダウンロードして、ユーザーが初めてアプリを起動する前に準備を整えることができます。 これにより、インストール時間が長くなり、初回実行時のエクスペリエンスが向上し、実行中のアプリからのダウンロードと同じダウンロードおよび検証ロジックが使用されます。

ダウンロードしたモデルをアプリ間で共有する

複数の独立したアプリが依存するライブラリまたは SDK を構築していて、各アプリが独自にダウンロードするのではなく、モデルの 1 つのディスク上のコピーを共有する場合は、独自のダウンロード ロジックを記述する代わりに、Windows ML モデル カタログ API を使用します。 詳細については、「 モデル カタログ API の概要」を参照してください。

次のステップ