MRが楽しい

MRやVRについて学習したことを書き残す

Unity AIのドキュメントを読む その57(モデルを実行する)

本日はUnityの技術調査枠です。
Unity AIのドキュメントを読みながら実際に操作を試して記事に残します。

Unity AI

以下のUnity AIのドキュメントを試しながら実行時のキャプチャをしていきます。
docs.unity3d.com

モデルを実行する

ワーカーを作成したらScheduleを呼び出してモデルを実行します。

worker.Schedule(inputTensor);

Unityエディター内でのモデルの最初のスケジューリングは推論エンジンがコードとシェーダーをコンパイルします。
このときは内部メモリを割り当てる必要があるため、遅くなる可能性があります。その後の実行はキャッシュにより高速化されます。

アプリケーションの起動時にテスト実行を含めると、初期読み込み時間を短縮できます。

推論を複数フレームに分割する

モデルを1レイヤーずつ実行するにはワーカーのScheduleIterableメソッドを使用します。このメソッドはIEnumeratorオブジェクトを作成します。

例えばモデルの実行に50ミリ秒かかる場合、1フレームで実行するとゲームプレイ中にカクツキが生じたり、フレームレートが低下したりする可能性があります。
よりスムーズなパフォーマンスを確保するには10フレームに分割して実行し、1フレームあたり5ミリ秒を割り当てます。

次のコードサンプルでは、​​モデルを1フレームごとに1レイヤーずつ実行してモデルの実行が終了した後にのみUpdateメソッドの残りの部分を実行します。

// GPUの速度を上げるには数値を大きく設定します。
const int k_LayersPerFrame = 20;

IEnumerator m_Schedule;
bool m_Started = false;

void Update()
{
    if (!m_Started)
    {
        // ExecuteLayerByLayerはモデルのスケジュールを開始します。
        // モデルのレイヤーを反復処理し、各レイヤーを順番にスケジュールするためのIEnumeratorを返します。
        m_Schedule = m_Worker.ScheduleIterable(m_Input);
        m_Started = true;
    }

    int it = 0;
    while (m_Schedule.MoveNext())
    {
        if (++it % k_LayersPerFrame == 0)
            return;
    }

    var outputTensor = m_Worker.PeekOutput() as Tensor<float>;
    // cpuCopyTensorは出力テンソルのCPUコピーです。
    // これにアクセスして変更することができます。
    var cpuCopyTensor = outputTensor.ReadbackAndClone();

    // ネットワークを再度実行するにはこのフラグをfalseに設定します。
    m_Started = false;
    cpuCopyTensor.Dispose();
}

以前作成した文字解析のサンプルスクリプトにフレーム分割の処理を組み込んでみました。

using UnityEngine;
using Unity.InferenceEngine;
using System.Collections;

public class SplitInferenceTest : MonoBehaviour
{
    public Texture2D inputTexture;
    public ModelAsset modelAsset;

    // Set a larger number for faster GPUs
    const int k_LayersPerFrame = 20;

    Model runtimeModel;
    Worker worker;
    public float[] results;

    IEnumerator m_Schedule;
    bool m_Started = false;
    Tensor<float> inputTensor;

    void Start()
    {
        Model sourceModel = ModelLoader.Load(modelAsset);

        // 入力モデルを実行し、出力にソフトマックスを適用する機能グラフを作成します。
        FunctionalGraph graph = new FunctionalGraph();
        FunctionalTensor[] inputs = graph.AddInputs(sourceModel);
        FunctionalTensor[] outputs = Functional.Forward(sourceModel, inputs);
        FunctionalTensor softmax = Functional.Softmax(outputs[0]);

        // 関数グラフをコンパイルして、ソフトマックスを使用してモデルを作成します。
        graph.AddOutput(softmax);
        runtimeModel = graph.Compile();

        // 入力データをテンソルとして作成する。
        inputTensor = new Tensor<float>(new TensorShape(1, 1, 28, 28));
        TextureConverter.ToTensor(inputTexture, inputTensor);

        // ワーカーを作成する
        worker = new Worker(runtimeModel, BackendType.GPUCompute);
    }

    void Update()
    {
        if (!m_Started)
        {
            Debug.Log("Starting inference...");
            // ExecuteLayerByLayer はモデルのスケジュールを開始します。
            // モデルのレイヤーを反復処理し、各レイヤーを順番にスケジュールするための IEnumerator を返します。
            m_Schedule = worker.ScheduleIterable(inputTensor);
            m_Started = true;
        }

        int it = 0;
        while (m_Schedule.MoveNext())
        {
            Debug.Log("Scheduling layer...");
            // 各レイヤーの実行が完了したことを確認します。
            if (++it % k_LayersPerFrame == 0)
                return;
        }

        Debug.Log("Inference completed.");
        // 結果を取得する。
        Tensor<float> outputTensor = worker.PeekOutput() as Tensor<float>;
        var cpuCopyTensor = outputTensor.ReadbackAndClone();
        results = cpuCopyTensor.DownloadToArray();

        // このフラグを false に設定してネットワークを再実行します
        m_Started = false;
        cpuCopyTensor.Dispose();
    }

    void OnDisable()
    {
        worker?.Dispose();
        inputTensor?.Dispose();
    }
}

以下の通り、処理が分割されて実行されます。