🗣️

[iOS 17] 音声認識におけるカスタム言語モデルのサポート

2023/10/21に公開

iOSには標準の音声認識フレームワークとして、Speech フレームワークというものが用意されている。日本語もサポートしており、オンデバイスでも動作可能。iOS 10の頃から使える。

が、最近だとOpenAIのWhisperもCore ML版が出て、iOSアプリで音声認識を使うなら、(少なくとも書き起こし用途なら)Whisperの方がいいかな…と思っていた。

https://twitter.com/shu223/status/1653737028213964800

しかしWWDC23のセッション「Customize on-device speech recognition」を見ると、かなり魅力的なアップデートがアナウンスされていた。

https://developer.apple.com/videos/play/wwdc2023/10101/

Speechフレームワークにおいてカスタム言語モデルの利用が可能になった、というもの。本記事はこれについて書いていく。

なお、音声認識における「音響モデル」「言語モデル」といったところの解説はわかりやすい記事が山程あると思うので本記事では割愛する。

以下、画像や引用表記は基本的に "Customize on-device speech recognition" セッションからの引用。

これまでの問題

Speechフレームワークに言語モデルもカプセル化されており、カスタマイズできなかった。

img

セッション内では、チェスアプリを例に取り、

img

相手が「クイーンズ・ギャンビット」という手を打ってきて、それに対して「アルビンのカウンターギャンビット」という手を打つべく、

"Play the Albin counter gambit. "

と音声入力すると(playにはチェスの手を打つという意味もあるらしい)、音声認識エンジンが「アルバムを再生してください」という音楽リクエストとして誤認識する、ということを言語モデルが固定であることによる問題ケースとして挙げていた。

img

この問題はめちゃくちゃわかる。SpeechであれWhisperであれ、「この会話の中ではそんな単語絶対に言わないんだけどな…」という認識結果が優先される [1] ことはしょっちゅうある。

言語モデルのカスタマイズ

冒頭でも述べたとおり、iOS 17からは SFSpeechRecognizer の言語モデルをアプリケーションに合わせてカスタマイズし、精度を向上させることが可能になった。

言語モデルのカスタマイズの一連の流れについて、セッションでは以下のように解説されている:

To get started with language model customization, first create a collection of training data.
(言語モデルのカスタマイズを始めるには、まず、トレーニングデータのコレクションを作成します。)

You can do this during your development process.
(これは、開発プロセス中に行うことができます。)

Then, in your app, you'll prepare the data, configure a recognition request, and then run it.
(次に、アプリで、データを準備し、認識要求を設定し、そして実行します。)

img

Data generation

トレーニングデータの収集について。

トレーニングデータは、アプリのユーザーが話しそうなフレーズを表すテキストの断片で構成される。

Speechフレームワークに新たに追加された SFCustomLanguageModelData クラスは、トレーニングデータのコンテナとして機能する。

img

上のコードでは、ResultBuilder DSLを利用したイニシャライザ [2] を利用している。

convenience init(
    locale: Locale,
    identifier: String,
    version: String,
    @SFCustomLanguageModelData.DataInsertableBuilder builder: () -> DataInsertable
)

上のコードで利用している SFCustomLanguageModelData.PhraseCount について、セッション内では以下のように解説されている:

You can provide an exact phrase or part of a phrase using a PhraseCount object.
(PhraseCountオブジェクトを使用して、正確なフレーズまたはフレーズの一部を提供することができます。)

A PhraseCount will also describe how many times the sample should be represented in the final data set.
(PhraseCountには、そのサンプルが最終的なデータセットで何回表現されるべきかも記述されます。)

This can be used to weight certain phrases more heavily than others.
(これは、特定のフレーズを他のフレーズよりも重く評価するために使用することができます。)

Only so much data can be accepted by the system, so balance your need to boost phrases against your overall training data budget.
(システムが受け入れることができるデータは限られているため、フレーズを増やす必要性とトレーニングデータ全体の予算とのバランスを取る必要があります。)

また、テンプレートを活用することで、一定のパターンに合致するサンプルを大量に生成することができる。

img

上のコードでは、SFCustomLanguageModelData.TemplatePhraseCountGenerator を利用して、チェスの手を表すサンプルを大量に生成している。

Here, I've defined three classes of words that together make up a chess move.
(ここでは、チェスの一手を構成する3つの単語のクラスを定義しています。)

The piece to move, which doubles as the file that I'm targeting, the royal piece that defines which side of the board to play on, and the rank to move to.
(移動する駒(ターゲットとするファイルを兼ねる)、盤のどの側でプレイするかを定義する王道の駒、そして移動するランクです。)

By putting them together into a pattern, I can easily generate data samples representing every possible move.
(これらをパターン化することで、あらゆる手を表すデータサンプルを簡単に生成することができる。)

Here, the count applies to the entire template, so I'll get 10,000 samples representing chess moves, divided evenly among all of the resulting data samples.
(ここでは、テンプレート全体に対してカウントしているので、チェスの手を表す1万個のサンプルを、すべてのデータサンプルに均等に割り当てることになります。)

データオブジェクトの作成が完了したら、ファイルにエクスポートしてアプリに組み込む:

try await data.export(to: URL(filePath: "/var/tmp/SampleApp.bin"))

また、カスタムな発音( Custom pronunciations )を定義して言語モデルに組み込むこともできる。

img

発音は X-SAMPA 形式で指定する。

https://ja.wikipedia.org/wiki/X-SAMPA

パーソナライズ

言語モデルをパーソナライズする方法もセッションでは解説されている。

以下、該当パートを引用する:

img

You can use the same API to train on data that your app can access at runtime.
(同じAPIを使って、アプリがランタイムでアクセスできるデータで学習することができます。)

You might do this to support usage patterns that are specific to your users, such as focusing on the chess openings and defenses that your user is trying to learn.
(例えば、ユーザーが学習しようとしているチェスのオープニングとディフェンスに焦点を当てるなど、ユーザーに特化した使用パターンをサポートするために、この方法をとることができます。)

You might also want to train on named entities.
(また、名前付きエンティティで学習したい場合もあります。)

Maybe your app supports network play against your user's contacts.
(あなたのアプリは、ユーザーの連絡先とのネットワーク対戦をサポートするかもしれません。)

And as always, respecting the user's privacy is paramount.
(そして、いつも通り、ユーザーのプライバシーを尊重することが最も重要です。)

img

For example, a communication app may want to boost commands to call contacts based on the frequency with which those contacts appear in the call history.
(例えば、コミュニケーションアプリでは、通話履歴に表示される頻度に基づいて、連絡先への通話コマンドを強化することができます。)

This kind of information should always stay on device.
(このような情報は、常に端末に残っている必要があります。)

You simply call into the same methods from within your app to generate a data object, write it to a file, and ingest it as shown earlier.
(このような場合、アプリ内から同じメソッドを呼び出して、データオブジェクトを生成し、ファイルに書き出し、先ほどのようにインジェストするだけです。)

img

ローカライズ

Once training data is generated, it is bound to a single locale.
If you want to support multiple locales within a single script, you can use standard localization facilities like NSLocalizedString to do so.
(学習データが生成されると、1つのロケールにバインドされます。1つのスクリプトで複数のロケールをサポートしたい場合は、NSLocalizedStringのような標準的なローカライズ機能を使用することができます。

img

デプロイ

カスタム言語モデルをアプリにデプロイするには、まず、SFSpeechLanguageModel クラスの prepareCustomLanguageModel メソッドを呼び出す。

img

このメソッドは、前のステップで生成したファイルを受け取り、後で使用する2つの新しいファイルを生成する。(このメソッド呼び出しには時間がかかるらしい)

カスタム言語モデルの利用

前ステップで生成された言語モデルを、SFSpeechRecognitionRequest に新たに追加された customizedLanguageModel プロパティに渡すことで、カスタム言語モデルを利用した音声認識が実行できる。

img

このとき、requiresOnDeviceRecognition プロパティに true をセットしておく必要があるようだ。

Sometimes, you need to keep data on the device where it's generated in order to respect the user's privacy.
LM customization supports this by never sending customization data over the network.
All customized requests are serviced strictly on device.
When your app constructs the speech recognition request, you first enforce that the recognition is run on device.
Failing to do so will cause requests to be serviced without customization.
(ユーザーのプライバシーを尊重するために、データが生成されたデバイスにデータを保存しておく必要がある場合があります。LM カスタマイズは、カスタマイズデータをネットワーク上に送信しないことで、これをサポートします。すべてのカスタマイズされたリクエストは、デバイス上で厳密に処理されます。アプリが音声認識リクエストを構築するとき、まず認識がデバイス上で実行されることを強制します。これを怠ると、カスタマイズされずに要求が処理されることになります。

サンプルコード

カスタム言語モデルを利用するサンプルアプリは、既に公式から提供されている。

https://developer.apple.com/documentation/speech/recognizing_speech_in_live_audio

トップの表記には iOS 12.0+とあり、

img

Start recordingボタンを押すとマイク録音が開始され、リアルタイムに音声認識結果が表示される、Speechフレームワーク登場時からあるサンプルなのだが、

img

実は文中には明記されているように、カスタム言語モデルを用いており、iOS 17以上でのみ利用できる。(冒頭のiOS 12+表記はおそらくただの修正忘れ)

The sample app doesn't run in Simulator, so you need to run it on a physical device with iOS 17 or later, or iPadOS 17 or later.
サンプルアプリはSimulatorでは動作しないため、iOS 17以降、またはiPadOS 17以降を搭載した物理デバイスで実行する必要があります。

なお、本サンプルには datagenerator フォルダ配下に、カスタム言語モデルのトレーニングデータ生成ユーティリティが同梱されている。

This sample code project includes a command-line utility named datagenerator that produces a training data file.
このサンプルコードプロジェクトには、トレーニングデータファイルを生成するdatageneratorという名前のコマンドラインユーティリティが含まれています。

脚注
  1. SFSpeechRecognitionRequest には従来から contextualStrings というプロパティがあり、システムの語彙にないフレーズを追加する機能はあったが、何度か使ってみたことはあるがそれほどの効き目はなかった。 ↩︎

  2. SFCustomLanguageModelData のイニシャライザの定義: https://developer.apple.com/documentation/speech/sfcustomlanguagemodeldata/4196191-init ↩︎

Discussion