朝日ネット 技術者ブログ

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

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

はじめに

こんにちは。朝日ネットでWebアプリケーションの開発を行っている tommy です。

前回Media Capture and Streams を使ってブラウザで音声を録音しファイルでダウンロードするところまで作ってみました。 今回はそのファイルをサーバにアップロードして音声認識を行う部分を作っていこうと思います。

音声認識をサーバ側で行うには

現在、音声認識を行おうと思った場合、例えば以下のようなものが考えられます。

1. 各種クラウドサービスを利用する

現在、音声データを送ると、テキストに変換してくれるサービスが数多く存在します[参考] 。 利用量に応じて料金が発生してしまいますが、これらを使えば手軽に音声認識を利用することができます。

2. Julius を利用する

Julius はオープンソースの音声認識エンジンです。 無料で自分のサービスに組み込んで音声認識をすることができます。

3. 自前で機械学習をする

最近では、Google が クラウドで機械学習を行うサービス を出していたりして、個人で機械学習を行うのもハードルが下がってきました。 どちらかというと、各種クラウドサービスに匹敵するレベルの学習データを集めるのが大変だと思います。 個人的には自前で1から作るというよりは、足りない部分を補うようなものを作り、組み合わせるのがいいと思います。

今回は、1. のクラウドサービスの一つである、Google の Cloud Speech API を使ってみようと思います。

プログラム作成

サーバ作成

まずは https でアクセス可能なWebサーバを用意します。今回は簡単に https サーバを立てることができる Google App Engine (以下GAE) を利用しようと思います。 最初に GAE のチュートリアルである、クイックスタートを行います。使用言語はGoです。こちらの詳細は割愛します1

Hello, world! を表示するところまでできたなら次へ進みます。

Cloud Speech API の有効化とAPI キーの取得

GCPのコンソールにて、Cloud Speech APIを有効化します。APIにたどり着くにはコンソール上部の検索窓を使うのがいいでしょう。

f:id:a-tommy:20180903140109p:plain

たどり着いたら、APIを有効にします。

f:id:a-tommy:20180903140338p:plain

ただし、Google App Engineと違い、課金設定が有効になっていないと利用することができません(課金設定は必要ですが、月に60分までの無料枠が存在します)。 課金設定が有効になっていない場合、有効にしてからAPIを有効化してください。

f:id:a-tommy:20180903141032p:plain

APIを有効化したなら次はAPI キーを取得します。 認証情報画面に行き、"認証情報を作成" からAPI キーを作成します。

f:id:a-tommy:20180903142025p:plain

しばらくすると作成されたAPIキーが表示されます。

f:id:a-tommy:20180903142400p:plain

このAPI キーをプログラムで使用します。("キーを制限" をするとAPI キーを不正利用されないように制限をかけることができます。実運用する場合は制限をかけておきましょう。)

ページ作成

続いて、プログラムを作成していきます。

まずは、前回の 音声を録音しFlacファイルをダウンロードするhtml を、サーバにFlacファイルをアップロードするように変更します。

--- flac.html   2018-07-23 14:28:50.043899179 +0900
+++ flacupload.html     2018-09-03 17:29:34.067595289 +0900
@@ -9,6 +9,7 @@
  <div>
   <button onclick="start()" id="start">開始</button>
   <button onclick="end()" id="end" disabled>終了</button>
+  <pre id="resultdest"></pre>
  </div>
  <script type="text/javascript"><!--
    var localStream,source,scriptNode;
@@ -86,17 +87,14 @@
      }
      var samples = mergeBuffers(encData, recLength);
      var blob = new Blob([samples],{ type: 'audio/flac' });
-     var a = document.createElement('a');
-     if (window.webkitURL) {
-       a.href = window.webkitURL.createObjectURL(blob);
-     } else {
-       a.href = window.URL.createObjectURL(blob);
-     }
-     a.download = fileName;
-     a.target = '_blank';
-     document.body.appendChild(a);
-     a.click();
-     document.body.removeChild(a);
+     var oReq = new XMLHttpRequest();
+     oReq.open("POST", "flacupload", true);
+     oReq.onload = function (oEvent) {
+       var resultdest = document.getElementById("resultdest");
+       resultdest.innerHTML = "";
+       resultdest.appendChild(document.createTextNode("結果:\n" + this.responseText));
+     };
+     oReq.send(blob);
    }
    function encodeFlac(buffer, recBuffers, flacParams){
      var meta_data;

コード全体

この HTML (と libflac4-1.3.2.js )を hello.go と同じフォルダに置きます。

サーバ側(GAE)プログラム作成

続いて、GAE で先ほどの HTML を表示するプログラムと、アップロードされた Flacファイルを Cloud Speech API に投げて音声認識するプログラムを作成します。 クイックスタートで作成された hello.go の handle 関数をいじります。

func handle(w http.ResponseWriter, r *http.Request) {
        if r.Method == "GET" {
                w.WriteHeader(http.StatusOK)
                var t = template.Must(template.ParseFiles("flacupload.html"))
                if err := t.Execute(w, nil); err != nil {
                        fmt.Println(err.Error())
                        return
                }
        } else {
                bufbody := new(bytes.Buffer)
                bufbody.ReadFrom(r.Body)
                apikey, err := ioutil.ReadFile("apikey")
                if err != nil {
                        fmt.Println(err.Error())
                        return
                }
                var json = `{
                  "config" : {
                    "languageCode" : "ja-JP",
                    "maxAlternatives" : 10
                  },
                  "audio" : {
                    "content" : "` + base64.StdEncoding.EncodeToString(bufbody.Bytes()) + `"
                  }
                }`
                jsonStr := []byte(json)
                ctx := appengine.NewContext(r)
                client := urlfetch.Client(ctx)
                url := "https://speech.googleapis.com/v1/speech:recognize?key=" + string(apikey)
                resp, err := client.Post(url, "application/json", bytes.NewBuffer(jsonStr))
                if err != nil {
                        http.Error(w, err.Error(), http.StatusInternalServerError)
                        return
                }
                defer resp.Body.Close()
                body, _ := ioutil.ReadAll(resp.Body)
                fmt.Fprintln(w, "response Body:", string(body))
        }
}

コード全体

コードの概要としては、GET ならば先ほどの HTML をそのまま表示して、POST ならば Flacファイルのアップロードだとして、Cloud Speech API にその音声ファイルを送信して、音声認識をしてもらいます。 Cloud Speech API を使う部分については後ほど説明します。

次に libflac.js を静的ファイルとしてそのままダウンロードできるようにするために、app.yamlを編集します。

runtime: go
api_version: go1

handlers:
- url: /libflac4\-1\.3\.2\.js
  static_files: libflac4-1.3.2.js
  upload: libflac4\-1\.3\.2\.js
- url: /.*
  script: _go_app

API キー

最後に、 apikey というファイルを作り、先ほど作成したAPI キーをそのまま保存します。

最終的に必要なファイルは以下になります。

  • apikey
  • app.yaml
  • flacupload.html
  • hello.go
  • libflac4-1.3.2.js

Cloud Speech API

上記のコードで実際に Cloud Speech API を使っている部分は以下の部分になります。

apikey, err := ioutil.ReadFile("apikey")
if err != nil {
        fmt.Println(err.Error())
        return
}
var json = `{
  "config" : {
    "languageCode" : "ja-JP",
    "maxAlternatives" : 10
  },
  "audio" : {
    "content" : "` + base64.StdEncoding.EncodeToString(bufbody.Bytes()) + `"
  }
}`
jsonStr := []byte(json)
ctx := appengine.NewContext(r)
client := urlfetch.Client(ctx)
url := "https://speech.googleapis.com/v1/speech:recognize?key=" + string(apikey)
resp, err := client.Post(url, "application/json", bytes.NewBuffer(jsonStr))
if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Fprintln(w, "response Body:", string(body))

API の仕様は APIs & Reference  |  Cloud Speech API Documentation  |  Google Cloud にあります。

音声認識をしたいだけならば、 https://speech.googleapis.com/v1/speech:recognize というURLに、key=[API キー] というクエリをくっつけて、認識したい音声ファイルと、オプションをJSON形式で投げつけるだけでできてしまいます。

JSON本体は、

var json = `{
  "config" : {
    "languageCode" : "ja-JP",
    "maxAlternatives" : 10
  },
  "audio" : {
    "content" : "` + base64.StdEncoding.EncodeToString(bufbody.Bytes()) + `"
  }
}`

の部分ですね。"config" にオプションを指定します。今回は言語が日本語。認識結果の最大数は 10個としています。 そのほかにも音声ファイルの形式の指定などが必要なのですが、Wavファイルか Flacファイルならば省略可能なため、この二つのオプションの指定のみで済んでいます。

"audio" のほうには認識したい音声ファイルを指定します。"content" ならば、base64 でエンコードした音声ファイルを渡します。巨大な音声ファイルを渡したい場合は、一度 Google Cloud Storage にアップロードしておいて、"uri" を指定する方法も可能です。

現時点では、Cloud Speech API は音声認識した音声データの時間が課金対象で、月に60分無料。それを超えたら15秒ごとに課金となります。詳しくは こちら

実行結果

以上のコードを App Engine にデプロイして実行してみると以下の様になります。

f:id:a-tommy:20180903160200p:plain

せっかくなので ハローワールド! を認識してみましょう。 結果は以下のようになりました。

f:id:a-tommy:20180903155923p:plain

補足

本記事では汎用性が高くなるように、API キーを用い JSON RPC を叩くコードを自前で書きましたが、各プログラミング言語用のライブラリも用意されておりそちらを使えば、より容易に利用できる可能性があります。

おわりに

今回は Cloud Speech API を用いて音声認識を行うコードを実際に作ってみました。 クラウドで提供されているサービスを利用することで非常に簡単に音声認識を行うことができました。

しかし、クラウドのサービスを使うにあたっての問題点として、利用方法によっては莫大な請求額になってしまう可能性があります。
個人での利用のみなら、そう高額にはならないでしょうが、サービスとして公開する場合には想定以上の利用にならないよう、何かしらの対策する必要があると思います。
最初に示したとおり音声認識用のクラウドサービスは Cloud Speech API 以外にもいろいろありますし、オープンソースの音声認識エンジンもありますので、目的に応じて最適なものを利用するのがいいと思います。

テキストデータを得ることのみが目的ならば、第1回 で使った Web Speech API がより多くのブラウザに対応してくれる様になってくれるのが一番うれしいですね。

次回は、実際に会話をしていく部分を作成してみたいと思います。

採用情報

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


  1. 私がこのクイックスタートをやった時には、途中の cd helloworld の部分は cd go/src/github.com/GoogleCloudPlatform/golang-samples/appengine/helloworld/ としないと先に進めませんでした。