2018年12月07日

Google Assistant Service プログラミング事始め

はてなブックマークに登録

前回の記事では Google Assistant Library ベースのプログラミングを通じて Google アシスタントの各機能を取り回す試みを行いました。今回はもうひとつの Google Assistant SDK である Google Assistant Service に目を向けてみます。

  • 当ブログ記事「Google Assistant Library プログラミングを楽しむ 」より
    Google LLC は Google Assistant SDK for devices として「Google Assistant Library」「Google Assistant Service」の二種類のセットを提供しています。前者は高水準で稼働環境は狭め、後者は低水準で広範な稼働環境に対応しており、この周到な構成に Google の本気度が窺えます。現時点では一日あたりのリクエスト数に制限はあるものの個人でも無償で利用できることが大きな魅力です。

Google Assistant Service と稼働環境

前回も引用した Google Assistant Library と Google Assistant Service の比較表を再掲します。2018年12月時点の公式記事より)

Google Assistant Service の最大の特長は、このように gRPC 対応のプラットフォーム+対応言語環境全般で利用可能であることです。現時点では Google Assistant Library ほどには多機能ではないものの、稼働環境が広く Windows PC や Mac はもとより小振りで消費電力が小さく IoT フロントエンドとして有用な Raspberry Pi Zero / Zero W 系ボードも Google アシスタントクライアントとして利用できることに関心を誘われます。

Google Assistant Service と Google Assistant Library の機能面での違いは SDK 全体のリリースノートを追うことで手早く俯瞰できます。

両者の機能差は今後変化する可能性がありますが、個人的には 2018年12月時点で Google Assistant Service 側にはない要素のうち実用面とのかねあいにおいて以下の三点に留意しています。

  1. Google Assistant Library とは異なりウェイクワードの待機・検知に対応していない
  2. ニュースの読み上げや Google Podcasts に未対応
  3. アラームに未対応 (リマインダは設定可能)

なお、上記 1. は外部の Hotword Detector との連携(後述)によりある程度補うことが可能です。

※余談ながら、Google Home デバイス実機とは異なり今のところ Google Assistant SDK ベースのプログラムの所作に google-home-notifier を絡ませることはできません。たしかに 8009 番ポートを使ったキャスト機構にはハードウェア側要件としての印象が強いものの、google-home-notifier を あち こち で利用しているファンとしてはちょっと残念です。

pushtotalk.py プログラムのこと

Google Assistant Service をセットアップ後の googlesamples/assistant/grpc/ フォルダには「pushtotalk.py」プログラムが配置されます。 このプログラムは名前の示すように Google Assistant Library に含まれる「hotword.py」とは異なり、ウェイクワード(ホットワード)の音声ではなく 'Press Enter to send a new request...' の CUI メッセージを添えた click.pause() API で物理的なトリガーを待って Google アシスタントとの対話を開始する内容で実装されています。
セットアップずみの SDK ディレクトリ下での pushtotalk.py の実行方法は以下の要領です。引数で指定したプロジェクト ID とモデル ID はホスト側へ記憶され、変更の必要がなければ次回以降は省略可能です。

$ pwd
/home/t/wk/GoogleAssistant

$ ls
env

$ source env/bin/activate

(env) $ googlesamples-assistant-pushtotalk --project-id [設定ずみのプロジェクトID] --device-model-id [設定すみのモデルID]

pushtotalk.py を手元の複数の環境で実行した様子の動画を以下に示します。

Windows
Mac
Linux (32bit)
Raspberry Pi 3 Model B+
Raspberry Pi Zero W

上の動画のRaspberry Pi 3 Model B+ / Zero W には手持ちの以下の以下のマイクとスピーカーを写真の要領で接続しています。

今回は、前回の hotword.py と対照的なこの pushtotalk.py を試作の下敷きとします。

Hotword Detector について

前掲の Google Assistant Service 側未対応の機能のうち、実用上の影響がもっとも大きいのはウェイクワードの待機・検知ができないことでしょう。Google Assistant Library プログラムにおいては Google Home デバイスと同様にハンズフリーでアシスタントとの応酬が可能であることを考えあわせると淋しく感じられますが、この点は外部の Hotword Detection ソフトウェアを併用することで補うことができます。

ただし、外部の Hotword Detector との連携は Google アシスタントネイティブでのウェイクワードサポートではなく、あくまでも Assistant API を呼び出すためのトリガーを外側に用意する手立てに他ならないため以下の注意が必要です。

  • ウェイクワード設定の柔軟性や認識精度はすべて Detector 側の要件である
  • Google アシスタントの発話中にウェイクワードで介入することはできない

こういった事情を理解した上で Hotword Detector を併用すれば、間口の広い Google Assistant Service を様々な環境でより便利に利用することができるでしょう。

Porcupine と Snowboy

現時点でのスマートスピーカー向きの代表的な Hotword Detector として、Picovoice (カナダ系)による Porcupine と、 KITT.AI (中国系) による Snowboy が挙げられます。後者はすでに随所で取り上げられていますね。どちらも機械学習に基づく精度の高さ・低負荷・マルチプラットフォーム対応・カスタマイズの柔軟性をアピールポイントとしており、後発の Picovoice は両者の比較記事を公開しています。

手元ではどちらも使い始めてまだ日が浅いのですが、今のところウェイクワードを認識する能力そのものに際立った性能差は感じておらず負荷の度合いについては未検証です。今の時点で把握している両者の一長一短を挙げてみます。

対応プラットフォームの広さ: Porcupine ◎

  • Porcupine
    • Raspberry Pi/ Android/ iOS/ watchOS/ Linux/ Mac/ Windows/ web browsers
  • Snowboy
    • Raspberry Pi/ 64bit Mac OS X/ 64bit Ubuntu 14.04/ iOS/ Android/ ARM64 (aarch64, Ubuntu 16.04)

カスタムウェイクワードへの対応: Snowboy ◎

  • Snowboy
    • 任意のカスタムウェイクワードの作成・使用が可能
    • 日本語を含む主要 15か国語 or "Other" から言語を指定可能
    • 肉声でのモデル作成に対応 〜 モデルはサーバへ保存され別話者分を含め追加学習が可能
    • 操作性のよいブラウザ I/F が提供されている
      (クリックで大きく表示)
  • Porcupine
    • モデル作成には専用の CUI ツール「Porcupine Optimizer」を使用する。同ツールのソースコードは非公開で Windows/ Mac/ Linux 向けバイナリが配布されている
    • ウェイクワードのバリエーションはライブラリに同梱の英単語辞書に含まれる語彙の範囲に限定される。複数単語の組み合わせは可。モデルファイルの作成方法は以下の要領
      $ pwd
      /home/t/wk/Porcupine

      $ tools/optimizer/linux/i386/pv_porcupine_optimizer -r resources/ -p linux -o . -w "OK google"
    • 特定の話者の肉声に基づくモデリング・学習を行うことはできない
    • モデルファイルはプラットフォームごとに独立しており(バンドル分も同様)、Windows/ Mac/ Linux 用以外のモデルファイルの作成には商用ライセンスが必要
    • 作成したモデルファイルには 90日間の使用期限が伴う。再作成に制限はない

手元では今のところ PC では Porcupine、Raspberry Pi では Snowboy の要領で使い分けています。下の動画はそれぞれの動作の様子です。

※ ふたつの動画でのウェイクワード検知時・対話終了時の応答音はいずれも Snowboy の resource/ ディレクトリ下の wave ファイルによるものです。これらが耳に馴染んだため Porcupine との連携においても同じ要領で使用しています

  • 32bit Linux 環境での Porcupine - pushtotalk.py 連携
  • Raspberry Pi Zero W での Snowboy - pushtotalk.py 連携

試作

前回記事での hotword.py (Google Assistant Library) ベースの試作に続き、今回は Google Assistant Service の pushtotalk.py を下敷きにプログラムを作成します。作業上の便宜から開発は PC で行いましたが、Google Assistant Service ベースなので指先に乗るサイズの Raspberry Pi Zero W ボードをはじめ広範な環境で実行可能であることに夢が広がります。オールインワンのダンボールキットも製品化されていますね。

今回はシンプルな切り口として前回の Google Assistant Library 版の各プログラムと同じ動きをするものを作ってみることにしました。Google Assistant Serivice プログラミングは奥が深くまだまだ習作の段階ではありますが、事始めとして取り組んだ内容を以下に掲載します。

1. 指定テキストに基づく音声合成と読み上げ

テーマ: Google アシスタントの音声合成機能を単体で利用する。いわゆる Text to Speech。

前回 Google Assistant Libray 版試作へのリンク

内容

  • プログラムを起動するとテキスト連続入力モードへ
  • エンターキー区切りで入力した任意のテキストがそのままアシスタントに読み上げられる
  • ウェイクワードは使用しない

デモ: 動画 27秒
  

(前回分)
  

考えたことなど

  • 前回と同様、まず TTS を試したいと考えた
  • ここでのポイントは、「音声データではなく任意のテキストで Assistant へ指示を渡す」ことにある。だが、Google Assistant Library に存在する send_text_query API と同等の機能を持つものが Google Assistant Service には見当たらず、現時点で Google Assistant Service プログラミングに関する情報はネットにも乏しい
  • ヒントを探してまず pushtotalk.py をあらためて読む。このコードでの指示と応答の応酬は事前に用意した対話用ストリーム上での start_recording() 〜 stop_recording() を経て start_playback() 〜 stop_playback() を呼び出す内容で巧みなく実装されており、Google Assistant Service においては音声データ以外の方法で Assistant とのやりとりが不可である可能性も頭をよぎった
  • 手を動かすうちに SDK ディレクトリ配下の「googlesamples/assistant/grpc/textinput.py」プログラムの存在に気づいた。コードへのリンクとこのプログラムの実行例を示す
    • textinput.py - github.com/googlesamples
      (env)t@PC-533:~/wk/GoogleAssistant$ ./textinput.sh 
      INFO:root:Connecting to embeddedassistant.googleapis.com
      : こんにちは
      <you> こんにちは
      <@assistant> こんにちは、TAnabeさん 
      どうしましたか?
      : 今何時?
      <you> 今何時?
      <@assistant> 時刻は、17:25です。
      : 今晩、雨降る?
      <you> 今晩、雨降る?
      <@assistant> 夜は、雨の心配はないでしょう
      今夜の山口は雨ではないでしょう。 気温10度、晴れるでしょう。
      ---
      (weather.com でもっと見る)
      : 
      
  • このように textinput.py は音声を一切使わずテキストの入出力のみで Assistant とやりとりを行う内容のシンプルな内容であり、Assistant への指示に音声ではなくテキストを指定可能であることをまっすぐ示している。それを実現するためには google.assistant.embedded.v1alpha2 の AssistConfig クラスの text_query メンバ("The text input to be sent to the Assistant. This can be populated from a text interface if audio input is not available.") を利用すればよいことを以下のコードで知った
    • textinput.py#L83-L100
      083|        def iter_assist_requests():
      084|            config = embedded_assistant_pb2.AssistConfig(
      085|                audio_out_config=embedded_assistant_pb2.AudioOutConfig(
      086|                    encoding='LINEAR16',
      087|                    sample_rate_hertz=16000,
      088|                    volume_percentage=0,
      089|                ),
      090|                dialog_state_in=embedded_assistant_pb2.DialogStateIn(
      091|                    language_code=self.language_code,
      092|                    conversation_state=self.conversation_state,
      093|                    is_new_conversation=self.is_new_conversation,
      094|                ),
      095|                device_config=embedded_assistant_pb2.DeviceConfig(
      096|                    device_id=self.device_id,
      097|                    device_model_id=self.device_model_id,
      098|                ),
      099|                text_query=text_query,
      100|            )
      
  • では、これに対する Assistant からの応答をテキストではなく音声データで受け取ることは可能か? textinput.py でのレスポンス処理箇所は以下の内容
    • textinput.py#L109-L121
      109|        text_response = None
      110|        html_response = None
      111|        for resp in self.assistant.Assist(iter_assist_requests(),
      112|                                          self.deadline):
      113|            assistant_helpers.log_assist_response_without_audio(resp)
      114|            if resp.screen_out.data:
      115|                html_response = resp.screen_out.data
      116|            if resp.dialog_state_out.conversation_state:
      117|                conversation_state = resp.dialog_state_out.conversation_state
      118|                self.conversation_state = conversation_state
      119|            if resp.dialog_state_out.supplemental_display_text:
      120|                text_response = resp.dialog_state_out.supplemental_display_text
      121|        return text_response, html_response
      
  • ここでふたたび pushtotalk.py のコードを追い、上と同じ self.assistant.Assist() ループ中の次の記述に注目した
    • pushtotalk.py#L149-L153
      149|            if len(resp.audio_out.audio_data) > 0:
      150|                if not self.conversation_stream.playing:
      151|                    self.conversation_stream.stop_recording()
      152|                    self.conversation_stream.start_playback()
      153|                    logging.info('Playing assistant response.')
      
  • textinput.py での self.assistant.Assist() レスポンスのループにおいては audio 要素にまったく触れていないが、あるいはテキストベースで指示を受けた場合の応答においても resp.audio_out.audio_data がセットされるのではないかと根拠なく想像。とりあえずループ内に print(len(resp.audio_out.audio_data)) 文を挟んで実行してみた。以下はその画面表示結果
    : 今何時?
    <you> 今何時?
    0
    1600
    1600
    (引用中略)
    1600
    1600
    244
    <@assistant> 時刻は、18:43です。
    
  • どうやら正解らしい。実際に resp.audio_out.audio_data を順次ファイルへ出力し、それを Audacity に Raw データとして取り込んだところ 16ビット 16000 Hz のモノラル PCM データであることが確認された。つまり、textinput.py では音声でのレスポンスが不要であるため参照されていないものの、入力データが音声であってもテキストであっても Assistant からは常に音声形式とテキスト形式の応答が返されるらしい
       
          (mp3 形式へ変換したもの)
  • それなら話は早い。ここでは音声ストリームデータの再生のみができれば良い。コードを追い、pushtotalk.py での対話に使用されている conversation_streamsource, sink の両メンバが googlesamples.assistant.grpc.audio_helpers.py の SoundDeviceStream のインスタンスであることを確認し、同クラス内部の _audio_stream の実体が sounddevice パッケージの RawStream であることを知り 前掲のコードに次の要領で再生処理を加えた
    import sounddevice as sd
    
               :
            text_response = None
            html_response = None
            s = sd.RawStream(
                samplerate=audio_helpers.DEFAULT_AUDIO_SAMPLE_RATE, 
                dtype='int16', 
                channels=1,
                blocksize=audio_helpers.DEFAULT_AUDIO_DEVICE_BLOCK_SIZE)
            for resp in self.assistant.Assist(iter_assist_requests(),
                                              self.deadline):
                assistant_helpers.log_assist_response_without_audio(resp)
                s.write(resp.audio_out.audio_data)
                s.start()
                if resp.screen_out.data:
                    html_response = resp.screen_out.data
                if resp.dialog_state_out.conversation_state:
                    conversation_state = resp.dialog_state_out.conversation_state
                    self.conversation_state = conversation_state
                if resp.dialog_state_out.supplemental_display_text:
                    text_response = resp.dialog_state_out.supplemental_display_text
            return text_response, html_response
               :
    
  • 以上の経緯を経て Google Assistant Service ベースでの TTS プログラムが形になった。前回の Google Assistant Library 版と同じく IFTTT 上に用意した「オウム返しアプレット」を併用

ソースコード

2. 利用者の発話内容をテキストへ変換

テーマ: Google アシスタントの音声認識機能を単体で利用する。いわゆる Speech to Text。

前回 Google Assistant Libray 版試作へのリンク

内容

  • ウェイクワードの検知に Porcupine を利用。対象ワードは「picovoice」
  • 「エディタを起動」でテキストエディタを起動
  • 発話内容のテキストを連続してエディタへ書き出す
  • この手のことはひと昔以上前から商用化されていたが、認識精度が段違いに向上しておりすでに口述筆記や印刷物のテキスト書き起こしの下書きにも使える水準と言ってよいだろう
  • いろいろ肉付けすれば PC 上で Google アシスタントを Cortana, Siri 風のツールとして利用できるかも

デモ: 動画 41秒
  

(前回分)
  

考えたことなど

  • 利用者の発話内容をアシスタント側が認識した結果のテキストを取得したい
  • そのためには以下の手順を踏めばよいことを pushtotalk のコードで知った
    • pushtotalk.py#L138-L148
      138|        for resp in self.assistant.Assist(iter_log_assist_requests(),
      139|                                          self.deadline):
      140|            assistant_helpers.log_assist_response_without_audio(resp)
      141|            if resp.event_type == END_OF_UTTERANCE:
      142|                logging.info('End of audio request detected.')
      143|                logging.info('Stopping recording.')
      144|                self.conversation_stream.stop_recording()
      145|            if resp.speech_results:
      146|                logging.info('Transcript of user request: "%s".',
      147|                             ' '.join(r.transcript
      148|                                      for r in resp.speech_results))
      

ソースコード

  • PicovoiceWithGoogleAssitantService_input.py - github.com/mkttanabe
    • Porcupine をインストールしたディレクトリ下へ配置して実行のこと
    • Porcupine/resource ディレクトリ下に前掲の ding.wav, dong.wav が必要
    • デモ動画ではパラメータに "--keyword_file_paths resources/keyword_files/picovoice_linux.ppn" を指定

3. 利用者の発話内容を他言語へ連続翻訳

テーマ: Google アシスタントの 音声認識 / 文意解釈 / 言語翻訳 / 応答文生成 / 音声合成 の各機能を利用する

前回 Google Assistant Libray 版試作へのリンク

内容

  • ウェイクワードの検知に Porcupine を利用。対象ワードは「picovoice」
  • 今回の Google Assistant Service 版では口頭指示により翻訳先言語を切り替える機能を追加した
  • 利用者が日本語の文章を発話するとアシスタントがその翻訳文を読み上げる
  • 一文ごとにウェイクワード等の指示は不要
  • 操作に手がかからないのでオフィスや店舗等の多言語環境用に翻訳専用端末として仕立てられるかも

デモ: 動画 2分30秒
  

(前回分)
  

ソースコード

  • PicovoiceWithGoogleAssitantService_translate.py - github.com/mkttanabe
    • Porcupine をインストールしたディレクトリ下へ配置して実行のこと
    • Porcupine/resource ディレクトリ下に前掲の ding.wav, dong.wav が必要
    • デモ動画ではパラメータに "--keyword_file_paths resources/keyword_files/picovoice_linux.ppn" を指定

4. 利用者の発話内容を復唱

テーマ: Google アシスタントの音声認識 / 音声合成機能を利用する

前回 Google Assistant Libray 版試作へのリンク

内容

  • ウェイクワードの検知に Porcupine を利用。対象ワードは「picovoice」
  • 利用者が発話したフレーズをオウム返しする
  • 前掲の「指定テキストに基づく音声合成と読み上げ」の音声入力版。いわば翻訳を伴わない Speech to Speech

デモ: 動画 39秒
  

(前回分)
  

ソースコード

  • PicovoiceWithGoogleAssitantService_echo.py - github.com/mkttanabe
    • Porcupine をインストールしたディレクトリ下へ配置して実行のこと
    • Porcupine/resource ディレクトリ下に前掲の ding.wav, dong.wav が必要
    • デモ動画ではパラメータに "--keyword_file_paths resources/keyword_files/picovoice_linux.ppn" を指定

しりとり

テーマ: Google アシスタントの音声認識 / 音声合成機能を利用する

前回 Google Assistant Libray 版試作へのリンク

内容

  • ウェイクワードの検知に Porcupine を利用。対象ワードは「picovoice」
  • 日本語の単語でアシスタントとしりとりを行う
  • しりとり向きの適当な単語辞書を使用
  • 日本語形態素解析に Janome ライブラリを利用

デモ: 動画 60秒
  

(前回分)
  


現時点ではプログラミングを行うための実践的な情報をあまり目にすることのない Google Assistant Library と Google Assistant Service を題材に手元で行った試みを二度に分けて紹介しました。幼児の年齢を迎えたばかりの Google アシスタントはこれからこれらの SDK とともに成長を重ねて行くことでしょう。未来へ向かう道すがらの愉しみがまたひとつ増えた思いです。


(tanabe)

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント