まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:speech

IBM Cloud から提供されている AI サービス IBM Watson の中で「音声→テキスト変換」を行う Speech to Text APIにおいて、2022/05/05 時点ではまだベータ版機能として提供されている "Speaker Labels" 機能を使ってみました。その様子をサンプルソースコードと併せて紹介します。

なお以下で紹介している様子および内容は 2022/05/05 時点のベータ版のものです。今後 API の実行方法や出力フォーマット、価格、提供しているソースコード等も含めて変更になる可能性もあることをご了承ください。


【Speech to Text サービスにおける Speaker Labels 機能とは】
一般的な Speech to Text サービスから提供されている機能の多くは「一人が話している前提」がありました。要は一人の人が話しているという前提で、その音声データをテキスト化する、というものでした。

IBM Watson Speech to Text サービスにおける Speaker Labels 機能はこの点を改良して、「複数人が話している可能性を考慮」した上で音声データをテキスト化するものです。なお、この機能は 2022/05/05 時点においてはベータ版として提供されており、英語に加えてスペイン語、ドイツ語、チェコ語、韓国語、そして日本語に対応しています。詳しくはこちらを参照ください:
https://cloud.ibm.com/docs/speech-to-text?topic=speech-to-text-speaker-labels


【サンプルとその使い方を紹介】
この Speaker Labels 機能を使った Node.js のサンプルアプリケーションを作って公開してみました。興味のある方はこちらから git clone するかダウンロードして使ってください:
https://github.com/dotnsf/s2t_betas


ソースコードを展開後の、アプリケーションの使い方を紹介します。まずアプリケーションを動かすためには Node.js v14 以上及び npm が必要なので、未導入の場合は自分のシステムにあったモジュールをインストールしておいてください:
https://nodejs.org/

また IBM Watson の Speech to Text サービスインスタンスの API Key およびサービス URL も必要です。無料のライトプラン※でも構わないので IBM Cloud 内に作成し、接続情報から API Key およびサービス URL (apikey の値と url の値)を取得しておいてください(すぐ後で使います):
2022050601

※無料のライトプランの場合、変換できるのは1か月間で 500 分ぶんのデータまで、という制約があります。


また実際に Speech to Text で変換する音声データファイルが必要です。特に今回は Speaker Labels 機能を使うため、二人以上で会話している際の音声データが必要です。自分で録音したものを使っても構いませんし、どこかでサンプルデータをダウンロードして用意していただいても構いません。以下の例では、こちらから提供されている日本語会話サンプルデータを使わせていただきました:
https://www.3anet.co.jp/np/resrcs/333020/

上述のページから提供されているサンプルデータをダウンロードし、使えそうな mp3 ファイルをソースコードの public/ フォルダ内にコピーしておいてください。とりあえず 007.mp3 というサンプルはいい感じに2名の男女が会話している様子のデータになっているので、以下はこのファイルをソースコード内の public/ フォルダにコピーできているものとして説明を進めることにします:
2022050602


会話の音声サンプルデータが public/ フォルダ以下に用意できたらアプリケーションを起動するための準備を(1回だけ)行います。まずソースコードフォルダ直下にある settings.js ファイルをテキストエディタで開き、取得した Speech to Text サービスの API Key とサービス URL をそれぞれ exports.s2t_apikey と exports.s2t_url の値として入力した上で保存します:
2022050603


そして依存ライブラリをインストールします。ソースコードフォルダ直下において、以下のコマンドを実行します:
$ npm install

これで起動の準備が整いました。最後にアプリケーションを起動します:
$ node app

成功すると 8080 番ポートでアプリケーションが起動します。実際に利用するにはウェブブラウザで http://localhost:8080/ にアクセスします。すると以下のような画面になります:
2022050601


左上にはソースコードの public/ フォルダにコピーした音声会話データのファイル名が一覧で表示されています。ここから 007.mp3 というファイルを選択してください(これが比較的わかりやすくていい感じの結果でした)。そして POST ボタンをクリックして Speech to Text を実行します:
2022050602


実行と同時に指定した音声ファイルの再生も開始します(つまり音が出ます)。並行して音声の解析が非同期に行われ、解析結果が少しずつ表示されていく様子を確認できます(ここまではベータ版の機能を使っていません):
2022050603


あるタイミングから確定した文節のテキスト内容が複数の色に分類されて表示されます。この色の分類が話している人の分類でもあります(下の結果では茶色の文字との文字になっているので、二人で会話している様子だと判断されていることになります):
2022050604


007.mp3 を最後まで解析し終えると以下のようになりました。(識別精度はともかく(苦笑))2つの文節の中で2人の人が会話している様子だった、と識別された様子がわかります:
2022050605


【サンプルソースコード内を紹介】
最後にこのアプリケーションのサンプルソースコードの内容を紹介しながら、どのように API を実行して、どのような結果を取得しているのか、という内容を紹介します。先に言っておくと、この Speaker Labels 機能を使う上で API の実行方法自体は(オプションを ON にする以外は)以前と全く同じです。実行結果に新しい情報が含まれるようになるので、その部分の対応が必要になります。 また該当部分はすべて app.js ファイル内にあるので、このファイルの内容と合わせて紹介します。

まず 27 行目で定義しているオブジェクトが Speech to Text 実行時のパラメータに相当するものです。この中で日本語変換モデル等を指定していますが、32 行目の speakerLabels: true によって、ベータ版機能である speakerLabels を有効に設定しています:
27: var s2t_params = {
28:   objectMode: true,
29:   contentType: 'audio/mp3',
30:   model: settings.s2t_model,
31:   smartFormatting: true,
32:   speakerLabels: true,
33:   inactivityTimeout: -1,
34:   interimResults: true,
35:   timestamps: true,
36:   maxAlternatives: 3
37: };

実際の音声→テキスト変換は 88 行目の processAudioFile() 関数で行っています。特にこの例では音声データファイルを一括変換する方法ではなく、WebSocket を使った非同期変換(少しずつ変換結果を受け取る方法)である recognizeUsingWebSocket() (90 行目)を使っています。そして SpeakerLabels を有効にしている場合、この実行結果(92行目)は2通り想定する必要があります。1つは「音声→テキスト変換結果」、もう1つは「どの部分を誰が話していたか、の判定結果」です(一括の同期変換を使った場合はこれらをまとめて取得できますが、今回は非同期変換を使っているためこれらの結果がバラバラに返ってくる可能性を考慮する必要があります):
90: var s2t_stream = my_s2t.s2t.recognizeUsingWebSocket( s2t_params );
91: fs.createReadStream( filepath ).pipe( s2t_stream );
92: s2t_stream.on( 'data', function( evt ){
        :



まず「音声→テキスト変換結果」が返ってきた場合です。この場合、92 行目の evt オブジェクト(=テキスト変換結果)は以下のような形で返されます:
        {
          result_index: 0,
          results: [
            { 
              final: true,
              alternatives: [
                {  //. 候補1
                  transcript: "音声メッセージが既存のウェブサイトを超えたコミュニケーションを実現",
                  confidence: 0.95,
                  timestamps: [
                    [ "音声", 0.36, 0.84 ],
                    [ "メッセージ", 0.84, 1.35 ],
                    [ "が", 1.35, 1.59 ],
                       :
                    [ "実現", 4.13, 4.7 ]
                  ]
                },
                {  //. 候補2
                  :
                }
              ]
            }
          ]
        }

まず変換結果をある程度の区切りでひとまとめにしています(ある程度の空白期間が生じるまでを1つの節とみなしています)。その区切りの番号が result_index 値です(上の例では 0 になっています)。そしてテキスト変換した結果が results 内に配列形式で格納されています。各配列要素の中に final というキーがあり、これが true の場合は節として変換結果が確定したことを意味します(false の場合は節が確定する前の、変換途中での結果が返されていることを意味します)。そして altervatives 内にその変換結果が可能性の高い順にやはり配列で格納されています。特にこの部分に注目してください:
                {  //. 候補1
                  transcript: "音声メッセージが既存のウェブサイトを超えたコミュニケーションを実現",
                  confidence: 0.95,
                  timestamps: [
                    [ "音声", 0.36, 0.84 ],
                    [ "メッセージ", 0.84, 1.35 ],
                    [ "が", 1.35, 1.59 ],
                       :
                    [ "実現", 4.13, 4.7 ]
                  ]
                },

文章としては「音声メッセージが既存のウェブサイトを超えたコミュニケーションを実現」というテキストに変換されていることに加え、その自信度が 0.95 であること、そして各単語が現れる音声開始からの通算秒数が timestamps という配列変数内に格納されています。この例だと音声スタートから 0.36 秒後から 0.84 秒後までの間に「音声」と話されていて、次に 0.84 秒後から 1.35 秒後までの間に「メッセージ」と話されていて、・・・といったように変換結果が分類されています(ここ、後で使います)。

次に変換結果として返される可能性のもう1つ、「誰がどの部分を話しているか」の結果が返される場合、evt 変数の内容は以下のようになります:
        {
          speaker_labels: [
            { 
              from: 0.36,
              to: 0.84,
              speaker: 0,
              confidence 0.67,
              final: false
            },
            {
              from: 0.84,
              to: 1.35,
              speaker: 0,
              confidence: 0.67,
              final: false
            },
              :
            {
              from: 4.13,
              to: 4.7,
              speaker: 1,
              confidence: 0.67,
              final: false
            }
          ]
        }

speaker_labels というキーが含まれている場合はこちらのケースと判断できます。そしてその中身は上の例であれば以下のような意味です:

・0.36 - 0.84 秒の間は 0 番目の人(自信度 0.67)
・0.84 - 1.35 秒の間は 0 番目の人(自信度 0.67) (この2つは同じ人)
   :
・4.13 - 4.7 秒の間は 1 番目の人(自信度 0.67)  (上とは別の人)

先程のテキスト変換結果の timestamps 値と合わせて、どの(何秒時点の)テキスト部分を何番目の人が話しているか、がわかるように speaker というラベルが付けられています。後はこれらをうまく組み合わせて、例えばテキストの色を分けて表示するようにしたものが提供しているサンプルアプリケーションです:
2022050600


なお、現時点での仕様としては以下のような制約があるようです:
・「2名で」話している前提で判断するよう最適化されている(実際には3名以上と判断される場合もあるが、あくまで2名の会話であることを想定した上で最適化されてラベルが付けられる)。
・speaker_labels の結果にも最終結果であることを示す final キーは存在しているが、final = true とならずに終わるケースが多い(なので、現状ここは無視してもよさそう)。


この辺りはあくまでベータ版での仕様なので、精度含めて今後の変更の可能性もあると思っています。ただ少なくともベータ版の現時点ではこの speaker_labels は無料で(無料のライトプランでも)使える機能のようで、今のうちから色々試してみたいと思いました。複数人の会話音声データから複数人の会話テキストを取り出せるようになると会議の議事録とかにも使えそうで、使い道の幅が大きく広がると期待しています。


連休中にふと気付いた、Bluemix / Watson デベロッパーとしては大きめのニュースです。

IBM Bluemix から提供されている Watson API 群の1つ、Text to Speech 。これは与えたテキストを自然な音声のオーディオデータにして返してくれる API サービスです。で、このサービスのカタログページ(https://console.ng.bluemix.net/catalog/text-to-speech/)をブラウザの言語設定を日本語にして参照すると、今(2015/Sep/23)も以前と特に変わらずこんな感じですが・・・
2015092301

 
ブラウザの言語設定を英語にして同ページにアクセスすると・・・
2015092302


こ、これはっ!
2015092303

 
念のため、Watson Developer 向けデモページ(http://text-to-speech-demo.mybluemix.net/)を開いてみると、言語サンプルに「日本語 - エミ(女性)」なる選択肢が含まれています!!サービスがいつの間にか日本語対応していたのか!?
2015092305


念のため、実際に API として利用できるかどうかを試してみました。Bluemix 上で Watson の Text to Speech サービスを追加し、接続情報(credentials 内の url と username と password)を参照します:
2015092307


この username と password の情報を使って、ウェブブラウザ(Chrome か FireFox)で以下の URL にアクセスします:
https://(usernameの値):(passwordの値)@stream.watsonplatform.net/text-to-speech/api/v1/synthesize?voice=ja-JP_EmiVoice&text=今日はいい天気ですね


するとこんな画面になって、女性の声で「今日はいい天気ですね」と聞こえてきます。データは audio/ogg で送られてくるので、この音声フォーマットを再生することのできるブラウザを使うか、或いは受け取る側でこの音声データを再生することができれば、その場で再生されるはずです:
2015092306


もちろん、これはあくまで API なので、実際のアプリケーションではそのまま再生する必要はなく、データベースに格納してもいいし、この音声データを別の API にポストしてもいいわけです。 ともあれ、いつの間にか Text to Speech が日本語対応していました!

なお、Text to Speech API のリファレンスはこちらを参照ください:
http://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/apis/#!/text-to-speech/


で、「エミ」って誰??

IBM Bluemix から提供されている認識型人工知能 API の1つ "Speech to Text" が2015年7月1日のアップデートで日本語に対応しました!
http://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/speech-to-text.html

音声ファイル(.wav など)をインプットとして与えると、その内容のテキストと、その変換の確度をテキストでアウトプットしてくれる、というサービスです。オンラインのデモサイトが用意されているので、まずはどのような内容なのかをデモで確認してみましょう:
https://speech-to-text-demo.mybluemix.net/

2015072001


まず "Transcribe Audio" と書かれている所を参照し、今回のデモで行う作業を IBM Watson の学習にご協力いただけるかどうかを設定します。学習にご協力いただける場合は "Allow Watson to learn from this session" を、この作業を学習してほしくない場合は "Opt Out" を選択してください。更にその下で今回の作業で使う音声の言語情報を設定します。今回は日本語音声でテストしたいので "Japanese broadband model (16KHz)" を選択しておきます。加えて実際の音声が聞こえるように PC のスピーカーを有効にしておきます。これだけで事前準備は完了です:
2015072002


では実際にサンプル音声データを使って認識させてみます。"Play Sample 1" ボタンをクリックします:
2015072003


サンプルの日本語音声が流れ始めます。同時にその音声の認識結果が表示されはじめます。
2015072006


最後まで再生が終わると、最終認識結果が表示されます。実際に試していただくとわかるのですが、Sample 1 は正しい結果に認識できていることがわかります。しかもちゃんと漢字になっていますね。。:
 2015072004


これが Watson Speech to Text API を使ったアプリの例です。このアプリで実現している内容と同様に、音声データをインプットとしてポストすると、認識結果がテキストで得られる、という API が用意されています。この API を自分のアプリケーションの中で利用することができる、というものです。

では実際にこの API をアプリケーションに組み込んで使ってみる、という具体例を紹介します。まずは Bluemix にログインし、実際のアプリケーションサーバーとなるランタイムを作成します。Speech to Text API は REST API なのでプログラミング言語に依存していませんが、今回は Java のサンプルを紹介するので Liberty for Java のランタイムを作成します:
2015072007


アプリ名は適当に(この例では kkimura-java-s2t と)付けておきます:
2015072008


ランタイムが作成できたら「概要」メニューから「サービスまたは API の追加」をクリックします:
2015072009


サービスの一覧が表示されます。今回の目的である Watson カテゴリ内の "Speech To Text" を選択します:
2015072010


"Speech To Text" サービスの説明が表示されます。日本語に対応していることが確認できます。この画面で「作成」ボタンを押して、このサービスが先程作成したランタイムに紐付けた形で利用できるようにします:
2015072011


ちなみに、この画面の下部にはサービスの価格に関する情報も表示されているので念のため確認ください。この API は音声データの長さで課金されます。最初の 1000 分が無料、それ以降は1分につき 2.10 円です。なお無料トライアル期間中のユーザーは課金対象ではありません:
2015072012


ランタイムに Speech to Text サービスが紐付けられると、ランタイムの環境変数からこのサービスを利用するための情報を参照できるようになります:
2015072013


この環境情報はこのような内容になっているはずです。"url" に API のエンドポイント(のベースとなる)値、そして "username" と "password" にはこの API を利用する際に必要になる認証情報が記載されています。これらの値を後で使います:
{
  "speech_to_text": [
    {
      "name": "Speech To Text-fc",
      "label": "speech_to_text",
      "plan": "standard",
      "credentials": {
        "url": "https://stream.watsonplatform.net/speech-to-text/api",
        "username": "(username)",
        "password": "(password)"
      }
    }
  ]
}

そして、実際に API にポストする日本語音声ファイルを用意します。今回はこちらのサイトから「あいうえお1(wav)」と書かれた .wav ファイル(aiueo1.wav)をダウンロードして使わせていただくことにします。もちろん他の音声ファイルでも構いません。現時点での対応フォーマットは flac/l16/wav です:
日本語音声サンプル


で、こんな感じのアプリケーションを作ってみました。まずはシンプルに音声ファイルを指定してアップロードする HTML ページのファイルです。アップロードするファイルは "audio_file" という名前で、./speech2text にポストします:
<html>
<head>
<title>Speech to Text</title>
</head>

<body>

<form name="frm" method="post" enctype="multipart/form-data" action="./speech2text">
<input type="file" name="audio_file"/><input type="submit" value="Submit"/>
</form>

</body>

</html>



そして、このページからアップロードされた音声ファイルを使って Speech To Text API を実行して、その結果を返すサーブレット(./speech2text)を以下の内容で作成します。なおこのサーブレットは Jakarta Commons HTTPClient 3.1JSON Simple 1.1.1 を使っています。必要であればこれらのモジュールも入手してください:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

public class SpeechToTextServlet extends HttpServlet {
  @Override
  protected void doPost( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException{
    String contenttype = "application/json; charset=UTF-8";
    String audio_type = "audio/wav";
    byte[] audio_file = null;
    String out = "";

    // Speech to Text を使うための username と password(環境変数にかかれていたもの)
String username = "(username)", password = "(password)"; req.setCharacterEncoding( "UTF-8" ); ServletFileUpload upload = new ServletFileUpload(); upload.setHeaderEncoding( "UTF-8" ); JSONParser parser = new JSONParser(); try{ FileItemIterator iterator = upload.getItemIterator( req ); while( iterator.hasNext() ){ FileItemStream item = iterator.next(); InputStream stream = item.openStream(); if( !item.isFormField() ){ String fieldname = item.getFieldName();
// audio_file フィールドに指定された音声ファイルのバイナリを取得する if( fieldname.equals( "audio_file" ) ){ audio_type = item.getContentType(); // 音声ファイルの Content-Type int len, size = 0; byte[] buffer = new byte[10*1024*1024]; //. 10MB MAX ByteArrayOutputStream baos = new ByteArrayOutputStream(); while( ( len = stream.read( buffer, 0, buffer.length ) ) != -1 ){ baos.write( buffer, 0, len ); size += len; } audio_file = baos.toByteArray(); // 音声ファイルのバイト配列 } } } if( audio_file != null ){ HttpClient client = new HttpClient(); byte[] b64data = Base64.encodeBase64( ( username + ":" + password ).getBytes() );
// /v1/recognize に音声ファイルのバイナリをポストする(パラメータで日本語音声であることを指定) PostMethod post = new PostMethod( "https://stream.watsonplatform.net/speech-to-text/api/v1/recognize?model=ja-JP_BroadbandModel" ); post.setRequestHeader( "Authorization", "Basic " + new String( b64data ) ); post.setRequestHeader( "Content-Type", audio_type ); ByteArrayRequestEntity entity = new ByteArrayRequestEntity( audio_file ); post.setRequestEntity( entity ); int sc = client.executeMethod( post ); if( sc == 200 ){
// ポスト結果(JSON)を UTF-8 テキストで取り出す byte[] b = post.getResponseBody(); out = new String( b, "UTF-8" ); // ※
// テキスト内の日本語認識結果と、その確度(自信)を取り出す JSONObject obj = ( JSONObject )parser.parse( out ); JSONArray results = ( JSONArray )obj.get( "results" ); if( results.size() > 0 ){ JSONObject result = ( JSONObject )results.get( 0 ); JSONArray alternatives = ( JSONArray )result.get( "alternatives" ); for( int i = 0; i < alternatives.size(); i ++ ){ JSONObject alternative = ( JSONObject )alternatives.get( i ); Double confidence = ( Double )alternative.get( "confidence" ); String transcript = ( String )alternative.get( "transcript" ); out = transcript + "\t" + confidence; // 結果をタブでつないで出力する //System.out.println( out ); } } } } }catch( Exception e ){ e.printStackTrace(); } res.setContentType( contenttype ); res.setCharacterEncoding( "UTF-8" ); res.getWriter().println( out ); } }

肝になっているのは赤字で記載した部分です。Speech to Text には何種類かの API がありますが、今回はシンプルに実行する /v1/recognize を使った例を紹介しています。この API はまず model パラメータで言語情報を指定します。今回は日本語音声ファイルを試したいので、model=ja-JP_BroadbandModel と決め打ちで指定しています。これ以外にも認識の途中経過を結果テキストに含めるような指定をすることもできますが、今回は認識した最終結果だけをその確度と一緒に返す仕様(デフォルト)で実行しています。

そして環境変数で確認した username と password を使った Basic 認証、および認識させる音声ファイルの Content-Type を HTTP ヘッダに追加し、音声ファイルのバイナリを本体としてする、という内容になっています。上記例は Java でその内容を実装していますが、同様の処理を記述すれば他の言語でも同じ処理を実行できるはずです。

この処理が成功すると、HTTP のレスポンス(上記コードの※の out の内容)として以下の様な JSON テキストが返されるはずです:
{
  "results": [
    {
      "alternatives": [
        {
          "confidence": 0.5490435361862183, 
          "transcript": "ああ いう よう "
        }
      ], 
      "final": true
    }
  ], 
  "result_index": 0
}

上記の中身の "transcript" が認識結果のテキスト(「あいうえお」のはずが「ああ いう よう」と聞こえちゃったんですね。。)、その確度が "confidence" に付与されています。 なので、この JSON フォーマットから目的の値だけを取り出して、上記例では "transcript" と "confidence" の値をタブ("\t")でつないで返す、という内容のサーブレットにしました。

したがって、このプログラムを実際に動かして、用意した aiueo1.wav をアップロードすると、このような結果がブラウザ画面に表示されることになります:
2015072001


Watson Speech to Text API の特徴の1つとして、「一般的な会話がなされている前提で、会話として確率の高そうな結果が優先される」ように感じています。上記例だと、一般的な会話の中には「あいうえお」という発音だけがなされる可能性は低いのも事実だと思っています。一方で「ああ、言うよ~」はより会話っぽいので、こういう結果の方がどちらかというと(会話として)可能性が高い、と判断するようです。良くも悪くも会話優先で変換してくれるような印象を持っています。


と、これが Watson の Speech to Text API の具体的な利用例です。パラメータで挙動をある程度コントロールすることもできますし、結果により詳しい情報を含めることもできます。音声というメディアをアプリケーションに組み組むのに便利な API だと思うので、様々なアプリアイデアの中で使ってみてください。


なお、Speech to Text API のリファレンスはこちらを参照ください:
http://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/apis/#!/speech-to-text/


このページのトップヘ