2018年11月30日

Google Assistant Library プログラミングを楽しむ

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

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

Google Home デバイスやスマートフォンアプリを通じて Google アシスタントの存在感はどんどん大きくなっています。その一方で現時点では Google Assistant SDK プログラミングのための実践的な情報を国内外を通じてあまり見かけないことが残念に思われました。ネットの奥深くに息づいている Google アシスタント陣営の一連の機能を本 SDK ごしに柔軟に呼び出すことができればこの強力で魅力的なサービスをさらに活用できるのではないでしょうか。

  • 音声認識
  • 文意の解釈
  • 応答文の生成
  • 音声合成
  • 言語翻訳

そんなわけで手元ではここしばらく Google Assistant SDK に向き合っています。今回はまず Google Assistant Library ベースでこれまでに行った調査と実験の内容を紹介します。

Google Assistant Library と稼働環境

前掲の表にも記載のあるように、今のところ Google Assistant Library の稼働環境は Python を利用可能な linux-armv7l または linux-x86_64 なプラットフォームに絞られます。周辺機器としてマイクとスピーカが必須。セットアップは下記ページからの説明にそって行えば問題ないでしょう。導入手順の詳細は随所で紹介されているためここでは省略します。

手元では作業用に 64bit lubuntu 環境の PC と Raspberry Pi 3 Model B+ を使っています。

hotword.py プログラムのこと

Google Assistant Library をセットアップ後の googlesamples/assistant/library/ フォルダには「hotword.py」プログラムが配置されます。実質 100行ほどの短い内容ですが、`sample` と言いながらこのプログラムを利用すればウェイクワードの検知を含め Google アシスタントとのいつものやりとりをそのまま実現することができます。抽象度が高くフロントエンドをこのようにシンプルに実装できることが Google Assistant Library の大きな特長です。
セットアップずみの SDK ディレクトリ下での hotword.py の実行方法は以下の要領です。引数で指定したプロジェクト ID とモデル ID はホスト側に記憶され、変更の必要がなければ次回以降は省略できます。

$ pwd
/home/t/wk/GoogleAssistant

$ ls
env

$ source env/bin/activate

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

hotword.py を PC / Raspberry Pi 3 で実行した様子の動画です。ここでは個人的な好みからウェイクワードへの反応音「ポコッ」の再生をコードに加えています。

  
上の動画のラズパイには手持ちの以下のマイク (USB 接続) とスピーカー (3.5mm オーディオジャック接続) を写真の要領でつないでいます。

一連の試作においてはこの hotword.py を下敷きにすることにしました。コンパクトで見通しのよい内容でありながらアシスタントとの応酬に必要な要素の一式が収められているためカスタマイズを試みるための土台としてはまさに好適でしょう。この記事の最後の項目に Google Assistant Library プログラミングを行う上での基本的な作法をまとめています。あわせて参照して下さい。

試作

今回手がけた試作をデモ動画と素のままのソースコードを添えて以下に掲載します。

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

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

内容

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

デモ: 動画 48秒
  

考えたことなど

  • まず最初に任意のフレーズをアシスタントに自由に喋らせてみたいと考えた。あの耳に馴染んだ声での発話はなかなか流暢で実用性があり、また、Google アシスタントの普及を背景とする記号的な存在感もある
  • だが、アシスタントの音声合成機能は利用者との対話を通じての一連の処理の中で内部的に呼び出されるものであり通常の操作でこれを単体で利用することはできない。また、SDK である Google Assistant Library にもそれを実現可能とする API は用意されていない
  • ここで TTS を実現するための要件をあらためて整理してみると、以下の二点に集約される
    1. 音声データではなく所定のテキストをアシスタント側へ引き渡すこと
    2. 上のテキストの内容をアシスタントに音声合成させてその音声データを再生すること
  • 上記要件 1. については Google Assistant Library にそのものずばりの「send_text_query」 API が存在することをリファレンスで知った。これは使える
    send_text_query(query)

    Sends |query| to the Assistant as if it were spoken by the user.

    This will behave the same as a user speaking the hotword and making a query OR speaking the answer to a follow-on query.

    Parameters: query (str) - The text query to send to the Assistant.
  • 問題は要件 2. だが、任意のフレーズを発話させる方法をひとつ思いついた。Google Assistant サービスをトリガーとする IFTTT アプレットにおいて 「What do you want the Assistant to say in response?」フィールドへ指定したフレーズは利用者が任意に指定すること可能であり、その内容は当該アプレット実行時のレスポンスとしてアシスタントによって発話される。ということは、Assistant サービストリガーを text ingredient オプションつきでアサインし、その text 引数の内容をそのまま response に指定することで「オウム返し」を行う IFTTT アプレットを用意すればよいのではないか? 指定必須の固定文言を「オウム返し」などと設定した上で「オウム返し ○○○○○」と話しかければ、アシスタントは当該アプレット実行のレスポンスとして「○○○○○」と発話するはず
  • さっそく以下の要領で「オウム返し」アプレットを用意して試したところ動画のように期待どおりの結果が得られた
  • あとは、任意のテキストの頭に "オウム返し " の文字列を挿入した上で前述の send_text_query API 経由でアシスタントへ引き渡せば良いだろう

ソースコード

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

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

内容

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

デモ: 動画 41秒
  

考えたことなど

  • 利用者の発話内容をアシスタント側が認識した結果のテキストを取得したい
  • そのテキストは下のように hotword.py 実行時にコンソールへきっちりエコーバックされるためプログラムで拾えそうだが、リファレンスをみてもその方法がわからない。ほしいのはこの中の「'今何時'」の部分なのだが、、
    ON_CONVERSATION_TURN_STARTED
    ON_END_OF_UTTERANCE
    ON_RECOGNIZING_SPEECH_FINISHED:
        {'text': '今何時'}
    ON_RESPONDING_STARTED:
        {'is_error_response': False}
    ON_RESPONDING_FINISHED
    ON_CONVERSATION_TURN_FINISHED:
        {'with_follow_on_turn': False}
    
  • hotword.py#L61-L63 にヒントがあった。以下のコードでは ON_CONVERSATION_TURN_FINISHED イベントの発生時に 「event.args['with_follow_on_turn'])」というパラメータを評価しており、それは上のコンソール出力内容末尾の二行に符合する
     61 |    if (event.type == EventType.ON_CONVERSATION_TURN_FINISHED and
     62 |            event.args and not event.args['with_follow_on_turn']):
     63 |        print()
    
  • ということは、「'今何時'」の部分を取得するには、ON_RECOGNIZING_SPEECH_FINISHED の発生時に「event.args['text']」というパラメータを参照すればよいのではないか?
  • 正解! これでアシスタント側が音声認識した結果のテキストを自由に拾えるようになった
  • せっかくデスクトップ環境なのでエディタの起動やテキスト入力といった GUI 操作の要素も加味することにした

ソースコード

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

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

内容

  • 利用者が日本語の文章を発話するとアシスタントがその英訳文を読み上げる
  • 一文ごとにウェイクワードや翻訳のための指示を入れる不要はない
  • 操作に手がかからないのでオフィスや店舗等の多言語環境用に翻訳専用端末として仕立てられるかも

デモ: 動画 52秒
  

考えたことなど

  • 周知のように Google アシスタントには 「"今何時ですか?" を英語に訳して」の要領で話しかけることでその文章を所定の言語へ翻訳する機能がある
  • ただ、毎回ウェイクワードを使うのも毎回「を○○語に」などと指定するのも微妙に面倒。発話した内容を自動的に翻訳してくれれば手間が省けるだろう
  • 手元では、前掲の「利用者の発話内容をテキストへ変換」と「指定テキストに基づく音声合成と読み上げ」の試作を通じて、次のふたつの処理を行う方法を把握している
    1. 利用者が喋った内容をアシスタントが認識した結果のテキストを取得する
    2. 音声ではなくテキストでアシスタントへ指示を送る
  • なので、発話したあとに上記 1. でその内容をテキストとして取得し、その末尾に「 を○○語に訳して」と加えた上で 2. の手順でアシスタントへ渡してやればいいだろう

ソースコード

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

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

内容

デモ: 動画 42秒
  

考えたことなど

  • 手元では、前掲の「利用者の発話内容をテキストへ変換」と「指定テキストに基づく音声合成と読み上げ」の試作を通じて次のみっつの処理を行う方法を把握ずみ
    1. 利用者が喋った内容をアシスタントが認識した結果のテキストを取得する
    2. 音声ではなくテキストでアシスタントへ指示を送る
    3. アシスタントに「オウム返し」をさせる
  • これらを利用する。自分の発話内容をテキストとして取得し、その先頭に前掲の IFTTT アプレット用のフレーズ「オウム返し 」を挿入してアシスタントへ渡してやる

ソースコード

しりとり

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

内容

  • 日本語の単語でアシスタントとしりとりを行う
  • しりとり向きの適当な単語辞書を使用
  • 日本語形態素解析に Janome ライブラリを利用

デモ: 動画 58秒
  

考えたことなど

  • スマートスピーカーまわりで Actions on Google や Alexa スキルを使った「しりとり」アプリを見かけるが、ここまでの内容を応用すれば Google Assistant Library ベースで実装できるのではないか
  • 単語の既出チェックや品詞の制限、単語の学習機能などちゃんとしたものを作ろうとすればそれなりに手がかかるが、必要最小限の単語リレーをするだけなら割と手早く作れるかも
  • しりとり向きの単語辞書が必要。ネット上のリソースをもとにそこそこの語彙数のものを用意することにする
  • 下記要領での処理を想定
    1. 利用者が単語を発話
    2. その内容をアシスタントが認識した結果のテキストを取得
    3. それを形態素解析にかけて読みを取得
    4. その読みの末尾文字に合致する一件の単語を辞書からピックアップしてアシスタントに発話させる
    5. 単語の末尾が「ン」なら終了

ソースコード

  • 別途

メモ: Google Assistant Library プログラミングの基本

hotword.py を通じて

前述のように Google Assistant Library のインターフェイスは抽象度が高くシンプルに扱うことができる。実ロジック 100 行程度の hotoword.py が Google Home デバイスとほぼ同等に Google Assistant と連携可能であることは興味深い。hotoword.py の内容にあらためて目を向けると、デバイス ID 等の管理情報の取り回し以外の実質的な処理は以下のごく短い内容のみであることが見てとれる。

  • hotword.py#L122-L145 より
    122 |     with Assistant(credentials, device_model_id) as assistant:
    123 |         events = assistant.start()
                     :
    144 |         for event in events:
    145 |             process_event(event)
    
    (リファレンスより)
    • class google.assistant.library.Assistant(credentials, device_model_id) - developers.google.com
      Client for the Google Assistant Library.

      Provides basic control functionality and lifecycle handling for the Google Assistant. It is best practice to use the Assistant as a ContextManager:
      with Assistant(credentials, device_model_id) as assistant:
      
      This allows the underlying native implementation to properly handle memory management.

      Once start() is called, the Assistant generates a stream of Events relaying the various states the Assistant is currently in, for example:
      ON_CONVERSATION_TURN_STARTED
      ON_END_OF_UTTERANCE
      ON_RECOGNIZING_SPEECH_FINISHED:
          {'text': 'what time is it'}
      ON_RESPONDING_STARTED:
          {'is_error_response': False}
      ON_RESPONDING_FINISHED
      ON_CONVERSATION_TURN_FINISHED:
          {'with_follow_on_turn': False}
      
      See EventType for details on all events and their arguments.

  • hotword.py#L47-L66 より
     47 | def process_event(event):
                      :
     56 |     if event.type == EventType.ON_CONVERSATION_TURN_STARTED:
     57 |         print()
     58 | 
     59 |     print(event)
     60 | 
     61 |     if (event.type == EventType.ON_CONVERSATION_TURN_FINISHED and
     62 |             event.args and not event.args['with_follow_on_turn']):
     63 |         print()
     64 |     if event.type == EventType.ON_DEVICE_ACTION:
     65 |         for command, params in event.actions:
     66 |             print('Do command', command, 'with params', str(params))
    
    (リファレンスより)
    • ON_CONVERSATION_TURN_STARTED - developers.google.com
      Indicates a new turn has started.

      The Assistant is currently listening, waiting for a user query. This could be the result of hearing the hotword or start_conversation() being called on the Assistant.
      • start_conversation() - developers.google.com
        Manually starts a new conversation with the Assistant.

        Starts both recording the user’s speech and sending it to Google, similar to what happens when the Assistant hears the hotword.

        This method is a no-op if the Assistant is not started or has been muted.
      • stop_conversation() - developers.google.com
        Stops any active conversation with the Assistant.

        The Assistant could be listening to the user’s query OR responding. If there is no active conversation, this is a no-op.
    • ON_CONVERSATION_TURN_FINISHED - developers.google.com
      The Assistant finished the current turn.

      This includes both processing a user’s query and speaking the full response, if any.

ポイント

  • Google Assistant との一連の応酬を開始するには google.assistant.library.Assistant クラスのインスタンスを生成し start() をコールする
  • 応酬中の状況はイベントベースで捕捉可能
    • 対話開始: ON_CONVERSATION_TURN_STARTED
    • 対話終了: ON_CONVERSATION_TURN_FINISHED
    • 後は EventType ごとに必要な処理を記述
  • start_conversation() によりウェイクワードなしで Assistant との対話を開始
  • send_text_query() により発話に代え任意のテキストを Assistant へ送出可能
  • stop_conversation() により Assistant との対話を任意に終了

(tanabe)

この記事へのコメント

1. Posted by 石田   2019年02月02日 18:41
はじめまして、send_text_query()でつまづいておりまして、コメントさせていただきます。

私の環境で
send_text_query('こんにちは')
を試すと、UnicodeEncodeError:になってしまいます。
ライブラリ内のquery.encode('ASCII')が原因のようですが、回避方法がわかりません。

File "/usr/local/lib/python3.5/dist-packages-linux-armv6l/google/assistant/library/assistant.py", line 215, in send_text_query
self._lib.assistant_send_text_query(self._inst, query.encode('ASCII'))
UnicodeEncodeError: 'ascii' codec can't encode characters

回避方法をご存知でしたら、ご教授願います。
ちなみに、
send_text_query('sing a song')のように英語のテキストだとうまく動作しています。

私の環境:
- AIY Voice Kit(pi zero w と voice bonnet)
- 最新のAIYプロジェクトイメージ

どうぞよろしくお願いいたします。
 
2. Posted by tanabe   2019年02月03日 22:53
こんにちは。これは Python よりの話題ですね。手元に AIY キットはありませんが、
「"UnicodeEncodeError: 'ascii' codec can't encode characters" sitecustomize.py」 といったキーワードで情報を確認されるのが良いのではないかと思います。
良い展開をお祈りします。

3. Posted by 石田   2019年02月04日 08:44
お返事ありがとうございます。
send_text_query(query) のテストは、PC環境、Pi3環境、どちらで行われたのでしょうか?
send_text_query()を日本語で使うのに、ライブラリー内に何か変更を加える必要はありましたか?
4. Posted by tanabe   2019年02月04日 09:55
おや? せっかくの機会ですから、あとはご自身で探索されることをおすすめします :-)

この記事にコメントする

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