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

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

タグ:text

ちょっとした目的で実験的に調査&開発していたウェブアプリの話がポシャりそうなので、アプリだけ公開することにしました。

やりたかったのはウェブアプリで
 1 日本語音声を認識して日本語テキスト化して、
 2 そのテキストを形態素解析して単語分解した上で、
 3 単語の出現頻度をタグクラウドで視覚化し、
 4 (ある程度以上の語彙が集まったら)テキスト内容から感情分析を行ってグラフ化する
 5 1~4をリアルタイムに行う(音声認識が途切れるタイミングで全情報を更新する)
というものでした。

技術的には色々な実現手段があるのですが、今回は実験的に作っていた経緯もあるので、
 1 Web Speech API
 2 TinySegmenter
 3 jQCloud
 4 色々使って独自実装
で作っています。

本当は4で IBM Personality Insight API とか使えると簡単でよかったのですが、今回は使っていないことを白状しておきます。また1の実装の都合上、PC 版 FireFox では動きません。後述のサンプルを利用する際には PC の Chrome か  Edge で試していただきたいです(互換性という意味では Android 版 Chrome でも動くことになっていますが、自分のテストでは認識精度がかなり悪かったので PC での利用をお勧めします)。また4の実装の中で利用回数に制約のある無料 API を使っているため、使いすぎて(使われすぎて)限度回数を超えてしまうとその月の間は動かなくなる、という点をご了承ください。

で、作って公開してみたのがこちらです。繰り返しますが PC 版 Chrome でアクセスしてください:
https://dotnsf.github.io/webspeechpi/


アクセスするとこのような画面が表示されます。右上の青いマイクボタンをクリックすると音声認識モードに切り替わります(初回のみマイクアクセスの許可を求められるので「許可」してください):
2022052901


音声認識モードに切り替わると青だったボタンは赤く変わります。またウェブページのタブに音声収集中であることを示す赤いマークが表示されます(ちなみにもう一度赤マイクボタンをクリックすると青マイクボタンに戻り、音声認識モードからも抜けます):
2022052902


この音声認識モードの状態でマイクに日本語で話しかけると、その文章が認識されて表示されます。ある程度の長さの無音状態が続くまでは1つの文章とみなして、たまに内容を変更・調整しながら認識を続けます:
2022052903


ある程度の長さの無音状態を認識すると、それまでに認識した文章を形態素解析し、名詞や動詞、形容詞といった、文章内容の肝となりうる単語がタグクラウドで表示されます:
2022052904


この認識文章の量が少ないとタグクラウドだけが表示されますが、ある程度以上の文章が認識されるとタグクラウドに加えて、そのテキスト内容から話している人の感情を5つの要素で分析して、その結果がレーダーチャートで表示されます。このタグクラウドとレーダーチャートは文章が入力されるごとに更新されます:
2022052905


と、まあこんな感じのものです。もともとはある業務目的のために作っていたものですが、不要になりそうだったので、せっかくなのでアプリ部分だけ公開することにしました。感情分析の精度は恥ずかしくなるようなものなので、ネタ程度に使ってみてください(笑)。本当は形態素解析ももう少し高度にできるのですが、無料公開できるものを作ろうとするとこんな感じになりました。

ウェブアプリそのものの(フロントエンド部分の)ソースコードはこちらで公開しています:
https://github.com/dotnsf/webspeechpi


感情分析 API はフロントエンドには含まれていません(公開していない理由はあまりに雑な実装で恥ずかしいため)。こちらの実装の中身に興味ある方がいたら教えてください。




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 は無料で(無料のライトプランでも)使える機能のようで、今のうちから色々試してみたいと思いました。複数人の会話音声データから複数人の会話テキストを取り出せるようになると会議の議事録とかにも使えそうで、使い道の幅が大きく広がると期待しています。


自分はテキストエディタにはこだわりがあります。

Vz エディタから始まり、vi(vim)、Emacs、・・と使ってきました。現在はこんな感じで使い分けています:
  • Java コードの記述には Eclipse
  • JavaScript の記述には ATOM
  • サーバーにターミナルログインして使う場合は vi(vim)
  • PC でマークダウンを記述する場合は Boostnote
  • それ以外はメモ帳かサクラエディタ

大きくは PC 環境なのか、サーバー環境なのかの違いです。基本的にサーバーにログインして使う場合は vi(vim) ばかり使ってます。一方 PC 環境の場合、Java だけは例外的に Eclipse でないと使いづらいのですが、それ以外はあまりこだわりはありません(最近、周囲の影響で ATOM を使い始めました)。実はメモ帳を使うことも結構多いのですが、議事録などはマークダウンで書くことが多く、その時は Boostnote を使ってます。


さて、クライアント環境では「好きなエディタをインストールして使う」ことも可能ですが、サーバー環境ではそうもいかないケースがあります。特に X Window システムが導入されていない CUI 環境の場合、そもそもテキストエディタの選択肢がほとんどなく、標準搭載されている vi を使うか、環境によってはたまに使うことの出来る Emacs を使うか、という形になることが多いと思ってます。ただいずれもキーバインドにクセがあり、サーバー環境初心者がメモ帳感覚でサーバー上のエディタを使うのが難しいという一面もあります。

そんな中、比較的普通(?)のキーバインド感覚で使えるのが "nano" エディタです。GNU プロジェクトの1つであり、GUI なしのターミナル環境で使える軽量エディタです。vi や Emacs だと「ファイルを保存」したり「エディタを終了」したりするにも専用のキーバインドを覚える必要があるので、初心者のとっかかりにはかなり高いハードルになってしまいますが、nano エディタは「常時表示されているメニューから選ぶだけ」なので、そのあたりのハードルは低めに設定されているといえます。 個人的にはあまり利用する機会のなかった nano エディタを調べてみました。


【インストール】
nano エディタは多くの環境で標準コマンドとして導入済みのことが多いと思いますが、導入されていない場合はインストールする必要があります。環境に合わせて、以下のいずれかのコマンドで導入してください:
(CentOS/RedHat 系の場合)
$ sudo yum install nano

(Ubuntu/Debian/Raspbian 系の場合)
$ sudo apt-get install nano


【起動】
コマンドラインからそのまま
$ nano

と入力することで nano エディタが新規ファイル作成モードで起動します。またはファイルの名前と一緒に
$ nano test01.txt

と入力すると、指定したファイルを編集するモードで起動します。


【画面】
実行中のターミナル内でフルスクリーンエディタとして起動します。画面下部にはメニューが常時表示されます。普通にカーソルキーで上下左右にカーソルを移動させることができ、キーを入力するとそのまま画面に表れます:
20170711


【メニュー】
以下、各メニューの項目を紹介します。メニュー項目を利用する場合はファンクションキーか Ctrl キーと表示されている文字を同時に押します( "^G" と表示されている場合は Ctrl + G です)。また以下で "M-*" という表記になっている場合は 「ESC キーを押してから * キー」という意味です:


^G (F1) : ヘルプ

以下のようなオンラインヘルプ画面を表示します。^Y / ^V で次/前ページへ移動、^P / ^N で次/前行で移動します( ^Y / ^V は編集画面でも同様に動きます)。^X でこのメニュー画面を終了します:
2017071102


^X (F2) : 終了

nano エディタを終了します。未保存の編集中のファイルがある場合は保存するかどうか確認した上で終了します(Y で保存、N で変更破棄、^C でキャンセル)。
2017071101


^O (F3) : ファイル書き出し

編集した内容をファイルに書き出します。ファイル名が指定されている場合はそのファイルに、ファイル名が指定されておらず、新規作成モードの場合はファイル名を指定して保存します:

2017071104

ESC キーとの併用でファイルフォーマットを指定したり、別のファイルの最後尾に追加する、という指定も可能です。


^J (F4) : テキスト整列

現在のコンソールサイズに合わせてテキストを整列し直します。



^R (F5) : ファイル読み込み

カーソル位置に別のファイルの内容を挿入します:

2017071103

^X で「コマンドの実行結果を挿入する」という指定もできるようです。


^W (F6) : 検索

指定したテキストを、現在のカーソル位置から後方に向かって検索し、最初に見つかった所へカーソルを移動します:

2017071105

ESC キーを組み合わせることで検索方向を前方に変更したり、正規表現指定が可能になったりします。


^Y (F7) : 前ページへ移動


^V (F8) : 次のページへ移動


^K (F9) : 1行カット

カーソルのある行をカットします。カーソル行は削除されますが、後述のペーストで元に戻せます。


^U (F10) : 1行ペースト

「カットのアンドゥ」で、^K でカットした行をペーストします。


^C (F11) : カーソル位置の確認


テキストが長くなって一画面で全てが表示しきれないような場合に、現在のカーソル位置が全体の何行目の、何文字目にあるのか、という情報を出力してくれます:

2017071106



^T (F12) : スペルチェック

内蔵されている spell を使ったスペルチェック機能らしいのですが、自分の環境ではうまく動きませんでした。。



以上、nano エディタの基本的な使い方について紹介しました。本格的なソースコード編集は手元の専用エディタを使うとして、サーバーにログインして作業が必要になった場合に vi や emacs が分からなくても、この nano エディタを使うことができれば、最小限の設定ファイル書き換えなどはできそうなので、これら2つのエディタに不慣れな人は重宝するかもしれません。



なお、nano エディタの解説はこちらの wiki にも詳しく紹介されていました。設定ファイルによってシンタックスハイライトなどもできそうです。こちらも参考にどうぞ:
https://wiki.archlinuxjp.org/index.php/Nano








 

このページのトップヘ