朝日ネット 技術者ブログ

朝日ネットのエンジニアによるリレーブログ。今、自分が一番気になるテーマで書きます。

オリジナルの音声アシスタントを作ろう (1) - Web Speech API

はじめに

こんにちは。朝日ネットでWebアプリケーションの開発を行っている tommy です。 最近、Siri や Google アシスタントのような、音声による機能操作が盛り上がってきています。 皆様も、オリジナルの音声アシスタントが欲しいなー。と一度は考えたことがあるでしょう。

ひと昔前には、音声認識は一般人にはとてもハードルの高い分野でした。しかし、時代が進み、誰もが気軽にこの技術に触れられるようになってきました。 この記事では、音声によってサービスを操作する機能を作るまでの課程を、何回かに分けて書いて行こうと思います。

音声アシスタント作成の流れ

  1. 音声入力
    • まずはサーバにユーザの"声"を届かせる必要があります。
  2. 音声認識
    • 音声データを、扱いやすいテキストデータに変換します。
  3. 応答・機能操作
    • テキストデータを解釈して、応答を返したり、機能を操作したりします。

まずは、 "1. サーバにどうやってユーザの声を届かせるのか" を考えていきたいと思います。

音声入力の選択肢として考えられるもの

音声をサーバに届けるには、例えば以下のような手段が考えられます。

Alexa スキル

スマートスピーカー Amazon Echo。その AI 部分を担当しているのが Amazon Alexaです。 Alexa は Echo に限らず、さまざまなデバイスに組み込むことができ、実際に Alexa 対応製品が発売されています。

スキルとはスマホで言うアプリのようなもので、Alexa 対応端末で有効にすることで使用できます。 これを作成することで、Alexa 対応端末から、あなたのサービスを音声操作することができます。

Web Speech API

Web Speech API は Speech Synthesis API (音声合成) と Speech Recognition API(音声認識)の二つから構成される JavaScript API です。

Can I use によると、2018年6月現在 Speech Synthesis API は多くのブラウザで実装されていますが、 Speech Recognition API は、Chrome が部分的にサポートしているだけとなります。 しかも、iOS版の Chrome では動かないので、実質、iPhone では使えません。

しかしながら、非常に簡単に音声認識を試すことができるので、本稿ではまず、こちらを触ってみようと思います。

Media Capture and Streams API

Media Capture and Streams API はブラウザから、カメラもしくはマイクにアクセスするための API です。こちらを使えば、生の音声データを取得することができます。 こちらは、Web Speech API と違い、すでに多くのブラウザで利用することができます

アプリ or 端末作成

このレベルから作成してしまえば、マイクにアクセスすることも容易です。
さらに、端末から作成するならば、Amazon Alexa を組み込む事も可能です。

本記事ではここから作ることは考えません。

Web Speech API (Speech Recognition API) を使ってみる

Speech Recognition API

Speech Recognition API は前述の通り、JavaScript による 音声認識API です。 こちらを使えば簡単に 「1. 音声入力」 と 「2. 音声認識」 までできてしまいます。

結論から言ってしまえば、

  1. SpeechRecognition のインスタンスを作成する。
  2. 認識したい言語を設定する。
  3. 結果を受け取るための onresult 関数を定義する。
  4. start 関数を呼び音声認識を開始する。

これだけで音声認識ができます。簡単ですね。

注意点

ブラウザからマイクにアクセスするためには、 https でページを表示 している必要があります。また、初回アクセス時に利用者へマイクへのアクセスの許可が求められます。
マイクがつながっていない端末だと音声認識開始時にエラーになります。

サンプルコード

以下に Speech Recognition API を使ったサンプルコードを記載します。 ボタンを押して、「Hey 朝日」と呼びかけた時だけ、「ご用件は何でしょう?」と返します。

このコードは以下のブラウザにて動作を確認しています。
* PC版 Google Chrome (バージョン: 67.0.3396.87)
* Android版 Google Chrome (バージョン: 67.0.3396.87)

後述しますが Android版 のほうは一部動かない属性があったり、挙動がおかしかったりします。

javascript

// インスタンスを作成
var recognition = new window.webkitSpeechRecognition();
// 認識したい言語の設定
recognition.lang = "ja-JP";
// 結果を受け取るためのonresult関数の定義
recognition.onresult = function(event){
  document.getElementById("output").innerHTML = "...";
  for (var i = event.resultIndex; i < event.results.length; i++){
    for (var j = 0; j < event.results[i].length; j++){
      if (event.results[i][j].transcript == "Hey 朝日") {
        document.getElementById("output").innerHTML = "ご用件は何でしょう?";
      }
    }
  }
};
// その他
recognition.continuous = true;
recognition.maxAlternatives = 10;
recognition.onspeechstart = function(){
  document.getElementById("output").innerHTML = "話しかけてください。";
};
recognition.onerror = function(){
  document.getElementById("output").innerHTML = "エラーが起きました。";
};
recognition.onnomatch = function(){
  document.getElementById("output").innerHTML = "認識できませんでした。";
};

html

<h1>Web Speech API</h1>
<input type="button" value="音声認識開始" onclick="recognition.start();">
<div id="output"></div>

Speech Recognition API を実際に使ってみるとわかりますが、認識精度はなかなかに高いです。 今回は、「Hey 朝日」で決めうちしたので、認識してくれないことがそこそこありますが、多少のぶれをカバーするようにすれば、ストレスなく話しかけることができると思います。

サンプルコード詳細

var recognition = new window.webkitSpeechRecognition();

SpeechRecognition のインスタンスを作成します。Chrome ではまだ先行実装段階なので、 webkit を頭につける必要があります。

recognition.lang = "ja-JP";

日本語を認識する様に設定します。

recognition.onresult = function(event){
  document.getElementById("output").innerHTML = "...";
  for (var i = event.resultIndex; i < event.results.length; i++){
    for (var j = 0; j < event.results[i].length; j++){
      if (event.results[i][j].transcript == "Hey 朝日") {
        document.getElementById("output").innerHTML = "ご用件は何でしょう?";
      }
    }
  }
};

結果を受け取るための関数を定義します。

SpeechRecognitionResultList は階層が深くてちょっとわかりづらいです。 以下のように書けば少しは見やすいでしょうか。

event = {
  results :
    [  // SpeechRecognitionResultList
      [  // SpeechRecognitionResult
        {  // SpeechRecognitionAlternative
           transcript : "認識結果",
           confidence : 認識結果のスコア
         }, ... 
      ], ...
    ], ...
  resultIndex : 新しい認識結果の最小のインデックス
};

音声が認識された時に、新しい認識結果が追加された SpeechRecognitionResultList が、onresult にわたってきます。event.resultIndex に新しい認識結果のうち一番小さい、配列のインデックスが入っています。

SpeechRecognitionResult は音声認識1回分のデータが入っています。認識結果の候補が複数ある場合は、SpeechRecognition.maxAlternatives の数まで SpeechRecognitionAlternative が返ってきます。SpeechRecognition.maxAlternatives のデフォルト値は 1 です。

SpeechRecognitionAlternative は音声認識結果の候補です。transcript にお目当ての認識されたテキスト。confidence にはその認識結果のスコアが入っています。

recognition.continuous = true;
recognition.maxAlternatives = 10;

SpeechRecognition.continuous を true にすると、連続して音声認識をすることができます。 デフォルトでは false なので、一回認識すると音声認識を終了してしまいます。 ただし、こちらは Android版 Chrome では動作しませんでした。

SpeechRecognition.interimResults は音声認識結果の候補として受け取る最大数を設定できます。 私の発音が悪いのか、「Hey 朝日」と言っても、西アサヒ と認識されることが多く、10個の候補のうち1つでも Hey 朝日 があればOK。という風にしました。 ただし、Android版 Chrome ではこの設定に関わらず常に複数の認識結果候補が返ってきました。

このほかにも、 SpeechRecognition.interimResults という属性があり、これを true にすると認識途中の結果でも順次 onresult に送られて来る様になります。 この認識結果を画面表示すると文章を打ち込んでいる様に見えて面白いです。 こちらは PC版 と比べて挙動が変ですが、Android版 Chrome でも動作しました。

recognition.onspeechstart = function(){
  document.getElementById("output").innerHTML = "話しかけてください。";
};
recognition.onerror = function(){
  document.getElementById("output").innerHTML = "エラーが起きました。";
};
recognition.onnomatch = function(){
  document.getElementById("output").innerHTML = "認識できませんでした。";
};

onresult 以外にもいくつかコールバック関数があります。 それぞれの詳しい発火タイミングについては、 SpeechRecognition - Web APIs | MDN をご覧ください。

現行の Android版 Chrome での問題点

以上に記載した通り、動かなかったり、挙動がおかしかったりする属性が存在します。 また、SpeechRecognitionAlternative.confidence がなぜか0になっている場合が多々あったり、 SpeechRecognition.interimResults を設定していないのに、一度の音声認識で複数回 onresult が呼ばれたりします。

まとめ

以上の通り、Web Speech API を使うと非常に簡単に音声認識をすることができます。 しかし、現状では対応ブラウザが少なく、対応していても一部動かないという状況です。
iPhone ユーザの自分としては iOS で全く使用することができないというのはいただけません。 ですので、次回は Media Capture and Streams API を使って、サーバに音声データを送ることに挑戦してみます。

採用情報

朝日ネットでは新卒採用・キャリア採用を行っております。