Android で質問に答える

Android の質問応答サンプル アプリ

このチュートリアルでは、TensorFlow Lite を使用して、自然言語テキストで構造化された質問に対する回答を提供する Android アプリケーションを構築する方法を示します。サンプル アプリケーションでは、自然言語 (NL) のタスク ライブラリ内のBERT 質問応答者( BertQuestionAnswerer ) API を使用して、質問応答機械学習モデルを有効にします。アプリケーションは物理的な Android デバイス用に設計されていますが、デバイス エミュレーター上でも実行できます。

既存のプロジェクトを更新する場合は、サンプル アプリケーションを参照またはテンプレートとして使用できます。既存のアプリケーションに質問応答を追加する方法については、 「アプリケーションの更新と変更」を参照してください。

質疑応答の概要

質問応答は、自然言語で提起された質問に答える機械学習タスクです。トレーニングされた質問応答モデルは、テキストの一節と質問を入力として受け取り、その文内の情報の解釈に基づいて質問に答えようとします。

質問応答モデルは、質問応答データセットでトレーニングされます。このデータセットは、テキストのさまざまなセグメントに基づく質問と回答のペアとともに読解データセットで構成されます。

このチュートリアルのモデルがどのように生成されるかについての詳細は、 「TensorFlow Lite Model Maker による BERT Question Answer」チュートリアルを参照してください。

モデルとデータセット

サンプル アプリでは、モバイル BERT Q&A ( mobilebert ) モデルを使用します。これは、 BERT (Transformers の双方向エンコーダー表現) の軽量かつ高速なバージョンです。 mobilebertの詳細については、研究論文「MobileBERT: a Compact Task-Agnostic BERT for Resource-Limited Devices」を参照してください。

mobilebertモデルは、Wikipedia の記事と各記事の質問と回答のペアで構成される読解データセットである Stanford Question Answering Dataset ( SQuAD ) データセットを使用してトレーニングされました。

サンプルアプリをセットアップして実行する

質問応答アプリケーションをセットアップするには、 GitHubからサンプル アプリをダウンロードし、 Android Studioを使用して実行します。

システム要求

  • Android Studioバージョン 2021.1.1 (Bumblebee) 以降。
  • Android SDK バージョン 31 以降
  • 開発者モードが有効になっている SDK 21 (Android 7.0 - Nougat) の最小 OS バージョンを搭載した Android デバイス、または Android エミュレータ。

サンプルコードを入手する

サンプル コードのローカル コピーを作成します。このコードを使用して Android Studio でプロジェクトを作成し、サンプル アプリケーションを実行します。

サンプルコードを複製してセットアップするには、次のようにします。

  1. git リポジトリ
    git clone https://github.com/tensorflow/examples.git
    
    のクローンを作成します
  2. 必要に応じて、スパース チェックアウトを使用するように Git インスタンスを構成して、質問応答サンプル アプリのファイルのみを用意します:
    cd examples
    git sparse-checkout init --cone
    git sparse-checkout set lite/examples/bert_qa/android
    

プロジェクトをインポートして実行する

ダウンロードしたサンプル コードからプロジェクトを作成し、プロジェクトをビルドして実行します。

サンプル コード プロジェクトをインポートしてビルドするには:

  1. Android Studioを起動します。
  2. Android Studio から、 [ファイル] > [新規作成] > [プロジェクトのインポート]を選択します。
  3. build.gradle ファイルを含むサンプル コード ディレクトリ ( .../examples/lite/examples/bert_qa/android/build.gradle ) に移動し、そのディレクトリを選択します。
  4. Android Studio が Gradle Sync を要求する場合は、[OK] を選択します。
  5. Android デバイスがコンピュータに接続されており、開発者モードが有効になっていることを確認してください。緑色のRun矢印をクリックします。

正しいディレクトリを選択すると、Android Studio は新しいプロジェクトを作成してビルドします。コンピューターの速度や、他のプロジェクトで Android Studio を使用しているかどうかによっては、このプロセスに数分かかる場合があります。ビルドが完了すると、Android Studio の[ビルド出力ステータス] パネルにBUILD SUCCESSFULメッセージが表示されます。

プロジェクトを実行するには:

  1. Android Studio から、 [実行] > [実行…]を選択してプロジェクトを実行します。
  2. アプリをテストするには、接続されている Android デバイス (またはエミュレーター) を選択します。

アプリケーションの使用

Android Studio でプロジェクトを実行すると、接続されたデバイスまたはデバイス エミュレーター上でアプリケーションが自動的に開きます。

質問回答者のサンプル アプリを使用するには:

  1. 主題のリストからトピックを選択します。
  2. 提案された質問を選択するか、テキスト ボックスに独自の質問を入力します。
  3. オレンジ色の矢印を切り替えてモデルを実行します。

アプリケーションは、文章テキストから質問に対する答えを特定しようとします。モデルがパッセージ内で回答を検出すると、アプリケーションは関連するテキスト範囲をユーザーに強調表示します。

これで、機能する質問応答アプリケーションが完成しました。次のセクションを使用して、サンプル アプリケーションがどのように動作するか、および実稼働アプリケーションに質問応答機能を実装する方法をよりよく理解します。

サンプルアプリの仕組み

アプリケーションは、自然言語 (NL) パッケージのタスク ライブラリ内のBertQuestionAnswerer API を使用します。 MobileBERT モデルは TensorFlow Lite Model Makerを使用してトレーニングされました。アプリケーションはデフォルトで CPU で実行されますが、オプションで GPU または NNAPI デリゲートを使用したハードウェア アクセラレーションを使用できます。

次のファイルとディレクトリには、このアプリケーションの重要なコードが含まれています。

  • BertQaHelper.kt - 質問回答者を初期化し、モデルとデリゲートの選択を処理します。
  • QaFragment.kt - 結果を処理し、フォーマットします。
  • MainActivity.kt - アプリの編成ロジックを提供します。

アプリケーションを変更する

次のセクションでは、サンプル アプリに示されているモデルを実行するように独自の Android アプリを変更するための主要な手順について説明します。これらの手順では、サンプル アプリを参照ポイントとして使用します。独自のアプリに必要な具体的な変更は、サンプル アプリとは異なる場合があります。

Android プロジェクトを開くか作成します

以下の残りの手順に従うには、Android Studio で Android 開発プロジェクトが必要です。以下の手順に従って、既存のプロジェクトを開くか、新しいプロジェクトを作成します。

既存の Android 開発プロジェクトを開くには:

  • Android Studio で、 [ファイル] > [開く]を選択し、既存のプロジェクトを選択します。

基本的な Android 開発プロジェクトを作成するには:

Android Studio の使用方法の詳細については、 Android Studio のドキュメントを参照してください。

プロジェクトの依存関係を追加する

独自のアプリケーションに特定のプロジェクトの依存関係を追加して、TensorFlow Lite 機械学習モデルを実行し、ユーティリティ関数にアクセスします。これらの関数は、文字列などのデータをモデルで処理できるテンソル データ形式に変換します。次の手順では、必要なプロジェクトとモジュールの依存関係を独自の Android アプリ プロジェクトに追加する方法について説明します。

モジュールの依存関係を追加するには:

  1. TensorFlow Lite を使用するモジュールで、モジュールのbuild.gradleファイルを更新して、次の依存関係を含めます。

    サンプル アプリケーションでは、依存関係はapp/build.gradleにあります。

    dependencies {
      ...
      // Import tensorflow library
      implementation 'org.tensorflow:tensorflow-lite-task-text:0.3.0'
    
      // Import the GPU delegate plugin Library for GPU inference
      implementation 'org.tensorflow:tensorflow-lite-gpu-delegate-plugin:0.4.0'
      implementation 'org.tensorflow:tensorflow-lite-gpu:2.9.0'
    }
    

    プロジェクトにはテキスト タスク ライブラリ ( tensorflow-lite-task-text ) が含まれている必要があります。

    このアプリをグラフィックス プロセッシング ユニット (GPU) で実行するように変更する場合は、GPU ライブラリ ( tensorflow-lite-gpu-delegate-plugin ) が GPU でアプリを実行するためのインフラストラクチャを提供し、デリゲート ( tensorflow-lite-gpu ) に互換性リストを示します。

  2. Android Studio で、 [ファイル] > [プロジェクトを Gradle ファイルと同期]を選択して、プロジェクトの依存関係を同期します。

ML モデルを初期化する

Android アプリでは、モデルで予測を実行する前に、パラメーターを使用して TensorFlow Lite 機械学習モデルを初期化する必要があります。

TensorFlow Lite モデルは*.tfliteファイルとして保存されます。モデル ファイルには予測ロジックが含まれており、通常は予測結果の解釈方法に関するメタデータが含まれています。通常、モデル ファイルは、コード例にあるように、開発プロジェクトのsrc/main/assetsディレクトリに保存されます。

  • <project>/src/main/assets/mobilebert_qa.tflite

利便性とコードの読みやすさを考慮して、この例ではモデルの設定を定義するコンパニオン オブジェクトを宣言しています。

アプリでモデルを初期化するには:

  1. コンパニオン オブジェクトを作成してモデルの設定を定義します。サンプル アプリケーションでは、このオブジェクトはBertQaHelper.ktにあります。

    companion object {
        private const val BERT_QA_MODEL = "mobilebert.tflite"
        private const val TAG = "BertQaHelper"
        const val DELEGATE_CPU = 0
        const val DELEGATE_GPU = 1
        const val DELEGATE_NNAPI = 2
    }
    
  2. BertQaHelperオブジェクトを構築してモデルの設定を作成し、 bertQuestionAnswererを使用して TensorFlow Lite オブジェクトを構築します。

    サンプル アプリケーションでは、これはBertQaHelper.kt内のsetupBertQuestionAnswerer()関数にあります。

    class BertQaHelper(
        ...
    ) {
        ...
        init {
            setupBertQuestionAnswerer()
        }
    
        fun clearBertQuestionAnswerer() {
            bertQuestionAnswerer = null
        }
    
        private fun setupBertQuestionAnswerer() {
            val baseOptionsBuilder = BaseOptions.builder().setNumThreads(numThreads)
            ...
            val options = BertQuestionAnswererOptions.builder()
                .setBaseOptions(baseOptionsBuilder.build())
                .build()
    
            try {
                bertQuestionAnswerer =
                    BertQuestionAnswerer.createFromFileAndOptions(context, BERT_QA_MODEL, options)
            } catch (e: IllegalStateException) {
                answererListener
                    ?.onError("Bert Question Answerer failed to initialize. See error logs for details")
                Log.e(TAG, "TFLite failed to load model with error: " + e.message)
            }
        }
        ...
        }
    

ハードウェア アクセラレーションを有効にする (オプション)

アプリで TensorFlow Lite モデルを初期化するときは、モデルの予測計算を高速化するためにハードウェア アクセラレーション機能の使用を検討する必要があります。 TensorFlow Liteデリゲートは、グラフィックス プロセッシング ユニット (GPU) やテンソル プロセッシング ユニット (TPU) など、モバイル デバイス上の特殊な処理ハードウェアを使用して機械学習モデルの実行を高速化するソフトウェア モジュールです。

アプリでハードウェア アクセラレーションを有効にするには:

  1. 変数を作成して、アプリケーションが使用するデリゲートを定義します。サンプル アプリケーションでは、この変数はBertQaHelper.ktの前半にあります。

    var currentDelegate: Int = 0
    
  2. デリゲートセレクターを作成します。サンプル アプリケーションでは、デリゲート セレクターはBertQaHelper.kt内のsetupBertQuestionAnswerer関数にあります。

    when (currentDelegate) {
        DELEGATE_CPU -> {
            // Default
        }
        DELEGATE_GPU -> {
            if (CompatibilityList().isDelegateSupportedOnThisDevice) {
                baseOptionsBuilder.useGpu()
            } else {
                answererListener?.onError("GPU is not supported on this device")
            }
        }
        DELEGATE_NNAPI -> {
            baseOptionsBuilder.useNnapi()
        }
    }
    

TensorFlow Lite モデルの実行にはデリゲートを使用することが推奨されますが、必須ではありません。 TensorFlow Lite でのデリゲートの使用の詳細については、 「TensorFlow Lite デリゲート」を参照してください。

モデルのデータを準備する

Android アプリでは、コードは、生のテキストなどの既存のデータをモデルで処理できるTensorデータ形式に変換することにより、解釈用のデータをモデルに提供します。モデルに渡す Tensor は、モデルのトレーニングに使用されるデータの形式と一致する特定の次元または形状を持っている必要があります。この質問応答アプリは、テキストの一節と質問の両方の入力として文字列を受け入れます。このモデルは特殊文字や英語以外の単語を認識しません。

パッセージテキストデータをモデルに提供するには:

  1. LoadDataSetClientオブジェクトを使用して、パッセージ テキスト データをアプリにロードします。サンプル アプリケーションでは、これはLoadDataSetClient.ktにあります。

    fun loadJson(): DataSet? {
        var dataSet: DataSet? = null
        try {
            val inputStream: InputStream = context.assets.open(JSON_DIR)
            val bufferReader = inputStream.bufferedReader()
            val stringJson: String = bufferReader.use { it.readText() }
            val datasetType = object : TypeToken<DataSet>() {}.type
            dataSet = Gson().fromJson(stringJson, datasetType)
        } catch (e: IOException) {
            Log.e(TAG, e.message.toString())
        }
        return dataSet
    }
    
  2. DatasetFragmentオブジェクトを使用して、テキストの各パッセージのタイトルをリストし、 TFL の質問と回答画面を開始します。サンプル アプリケーションでは、これはDatasetFragment.ktにあります。

    class DatasetFragment : Fragment() {
        ...
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            val client = LoadDataSetClient(requireActivity())
            client.loadJson()?.let {
                titles = it.getTitles()
            }
            ...
        }
       ...
    }
    
  3. DatasetAdapterオブジェクト内でonCreateViewHolder関数を使用して、テキストの各パッセージのタイトルを表示します。サンプル アプリケーションでは、これはDatasetAdapter.ktにあります。

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemDatasetBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return ViewHolder(binding)
    }
    

ユーザーの質問をモデルに提供するには:

  1. QaAdapterオブジェクトを使用して、モデルに質問を提供します。サンプル アプリケーションでは、これはQaAdapter.ktにあります。

    class QaAdapter(private val question: List<String>, private val select: (Int) -> Unit) :
      RecyclerView.Adapter<QaAdapter.ViewHolder>() {
    
      inner class ViewHolder(private val binding: ItemQuestionBinding) :
          RecyclerView.ViewHolder(binding.root) {
          init {
              binding.tvQuestionSuggestion.setOnClickListener {
                  select.invoke(adapterPosition)
              }
          }
    
          fun bind(question: String) {
              binding.tvQuestionSuggestion.text = question
          }
      }
      ...
    }
    

予測を実行する

Android アプリで、 BertQuestionAnswererオブジェクトを初期化したら、自然言語テキストの形式でモデルへの質問の入力を開始できます。モデルは、テキストの一節内で答えを特定しようとします。

予測を実行するには:

  1. モデルを実行し、回答を特定するのにかかる時間を測定するanswer関数 ( inferenceTime ) を作成します。サンプル アプリケーションでは、 answer関数はBertQaHelper.ktにあります。

    fun answer(contextOfQuestion: String, question: String) {
        if (bertQuestionAnswerer == null) {
            setupBertQuestionAnswerer()
        }
    
        var inferenceTime = SystemClock.uptimeMillis()
    
        val answers = bertQuestionAnswerer?.answer(contextOfQuestion, question)
        inferenceTime = SystemClock.uptimeMillis() - inferenceTime
        answererListener?.onResults(answers, inferenceTime)
    }
    
  2. answerからの結果をリスナー オブジェクトに渡します。

    interface AnswererListener {
        fun onError(error: String)
        fun onResults(
            results: List<QaAnswer>?,
            inferenceTime: Long
        )
    }
    

モデル出力を処理します

質問を入力すると、モデルはパッセージ内で最大 5 つの可能な回答を提供します。

モデルから結果を取得するには:

  1. 出力を処理するリスナー オブジェクトのonResult関数を作成します。サンプル アプリケーションでは、リスナー オブジェクトはBertQaHelper.ktにあります。

    interface AnswererListener {
        fun onError(error: String)
        fun onResults(
            results: List<QaAnswer>?,
            inferenceTime: Long
        )
    }
    
  2. 結果に基づいてパッセージのセクションを強調表示します。サンプル アプリケーションでは、これはQaFragment.ktにあります。

    override fun onResults(results: List<QaAnswer>?, inferenceTime: Long) {
        results?.first()?.let {
            highlightAnswer(it.text)
        }
    
        fragmentQaBinding.tvInferenceTime.text = String.format(
            requireActivity().getString(R.string.bottom_view_inference_time),
            inferenceTime
        )
    }
    

モデルが一連の結果を返したら、アプリケーションは結果をユーザーに提示するか、追加のロジックを実行することで、それらの予測に基づいて動作できます。

次のステップ