音響モデルを調整し、音声認識の精度を高める

適切な戦略とテスト手段で音声入力を微調整し、認識の精度を高める

適切に作成されていない音響モデルを扱うのは苛立たしいものです。それは、音声認識分野の初心者が独自の特定話者モデルを扱っている場合はなおのことです。キーボードやマウスによる入力は、比較的動作が確実で、オペレーティング・システムによって容易に解釈されますが、それとは異なり、音声認識プログラムに対する音声入力は、それほど確実ではなく、その認識精度は音響モデルの幅と深さに大きく依存します。プログラマーが認識エラーを分析するプロセスは、テスト手段を作成して使用することで容易になります。エラー率の妥当な目標としては、10 回中 5 回のエラーを 1000 回に 1 回未満にすることです。そのために、Python と PostgreSQL を使って作成したテスト手段を用いる方法を学びましょう。

Colin Beckingham, Writer and Researcher, Freelance

Colin Beckingham はカナダのオンタリオ州東部に住むフリーランスの研究者であり、ライターであり、プログラマーでもあります。キングストンの Queen's University で学位を取得している彼は、園芸、競馬、教育、行政サービス、小売業、旅行/観光業などにも関わってきました。彼はデータベース・アプリケーションの作成者であり、数え切れないほどの新聞記事や雑誌記事、オンライン記事を執筆しており、また Linux でのオープンソース・プログラミングや VoIP、音声制御アプリケーションも研究しています。



2012年 7月 12日

用語

  • 文法: 1 つの単語、または複数単語の組み合わせの有限集合。音声認識プログラムは、この集合の中から単語または複数単語の組み合わせを選択することができます。
  • 書記素: ある 1 つの単語がどのように書かれ、視覚的に出力されるかを表すもの。
  • 語彙素: ある 1 つの単語がどのように書かれ、発音されるかを同じ単語の異なる形態も併せて全般的に表すもの。
  • 語彙目録: 語彙素のリスト。ここでは、ある 1 つの単語がどのように書かれ、発音されるかの両方を各行に含むファイル。
  • 音素: ある音声を表す標識。いくつかの音素を連結することで、ある 1 つの単語がどのように聞こえるかが表されます。
  • PLS: これらの用語についての詳細は「参考文献」に挙げた「発音辞書仕様」(Pronunciation Lexicon Specification: PLS) へのリンクを参照してください。

VoxForge チュートリアルの説明に従い、HTK (Hidden Markov Model Toolkit)、Julius、CMU Sphinx などのツールを使用すると、単純な特定話者音響モデルを容易に作成することができます (これらのツールの詳細については「参考文献」を参照)。すると 2、3 時間もかからずに「2001年宇宙の旅」の HAL 9000 コンピューターが出来上がり、皆さんのコマンドに応答して皆さんと会話するようになり、皆さんがトイレ休憩でワークステーションを離れても、部屋の鍵をかけられてしまう可能性はほとんどありません。

ただし初心者の場合、最初のモデルの認識精度が受け入れがたいほど低いことに驚くことがあります。そして音声制御の実験をあきらめ、この分野はある程度の認識精度が得られる程は進歩していない、と考えてしまいがちです。しかしその考えは事実とはかけ離れています。

さらに、おそらくもっと深刻な事態は、認識エラーが多ければ多いほど、また明らかに期待通りの動作をしない認識装置に合うようにマイクに向かって注意深く話そうと一生懸命になればなるほど、期待通りの結果を得ようと不自然な発声方法になり、より緊張した声になってしまう可能性が高まることです。しかし音声認識の精度を高めるためには、リラックスした声で入力するとよい正当な理由があるのです。

この問題への対策として、より多くの音声サンプルを単純に追加すると効果的かもしれません。また、構成済みの不特定話者モデルを適用する方法も考えられます (その方法については VoxForge と CMU Sphinx のリソースの説明を参照してください)。あるいは第 3 の方法として、プログラミング・ツールを使用して、認識精度が低い問題に焦点を絞り、根本的な問題の修正方法を考え出すという選択肢もあります。

認識精度の低さを改善する上で、プログラマーは重要な役割を果たすことが可能です。作業環境を記述するツールを利用すると、認識精度の低さの原因を特定することができ、即座に基本的な問題や潜在的なソリューションに集中して取り組めるようになります。

HTK にはモデルの作成フェーズで利用できるツールが数多く用意されており、Julius は認識フェーズにおいてガイダンスとなるメッセージが表示されます。しかし、これらの方法には限界があります。例えば、HTK や Julius を作成した人達は、どのような文法をユーザーが使用するのかに関して何も想定していません。選択した文法が原因で発生するエラーの処理はユーザーが行う必要があります。

これから先のセクションでは、発生しがちな特定の問題について説明し、ある特別な手段に問題の特定や解決に役立つ可能性があるかどうかについて、例と注釈を付け加えながら説明します。

問題のタイプ

認識精度を低下させ得るエラーのタイプについて調べてみると、エラーの原因にはさまざまなものがあることがわかります。

  • コンピューター業界ではおなじみの GIGO (Garbage In, Garbage Out) の概念 (つまり、「入力が不適切であれば、得られる出力も不適切なものになる」という概念) は「モデルが曖昧である」という問題につながります。音響モデルの作成プロセスに提供されるサンプル・プロンプトが明確で意味のあるものでない限り、HTK は適切なモデルを作成することができません。こうした例としては、録音された .wav ファイルと組み合わされているプロンプトが不適切な場合や、HTK は .wav ファイルを受け付けるものの、(例えば音声レベルが不適切などの理由により) うまく解釈できない場合、単語間の短い沈黙が原因で音声の一部が認識し損なわれている場合、クリック・ノイズ、ポップ・ノイズ、背景音などが挿入されてしまっている場合などがあります。この問題の解決方法としては、音声ファイルに問題がないか調べ、場合によっては録音し直します。
  • 皆さん以外の他者の声で訓練された特定話者モデルを使用しようとすると、「モデルの幅が狭い」という問題が生じます。この場合、おそらく結果はお粗末なものになります。また、皆さん自身が有線接続のヘッドセットを使用してモデルを訓練し、生成されたモデルを使用して Bluetooth ヘッドセットでコマンドを発行しようとすると、まったく同じ声を発しているにもかかわらず、おそらく認識精度は 50% 未満になります。しかもさらに、同じヘッドセットであっても別のマシンに接続すると、やはり認識精度は期待外れの結果となります。これはモデルの幅が不適切であることによる問題とも言え、問題が発生する話者、機器、プラットフォーム専用に音声ファイルを追加することによってモデルを改善することができます。
  • 音響モデルには幅と深さの両方が必要です。同じ人が同じことを言う場合であっても、日によって感情の状態、雰囲気などが変わってくるため、言い方は異なります。動作プラットフォーム、話者、ヘッドセットに何も変更がなくても、話者が風邪を引いている場合、苛立っている場合、少し酔っている場合などは、話者の声は大幅にあるいは微妙に変わってきます。それは別の条件下で訓練されたモデルを混乱させるには十分であり、「モデルが浅い (その 1)」という問題につながります。この問題に対しては、語彙目録を詳細に検証して単語の聞こえ方を正確に記述し、必要に応じて語彙目録のエントリーを編集または追加することで、モデルの深さを増すことができます。
  • モデルが浅い (その 2)」という問題は、上記のポイントと似ていますが、まったく同じではありません。同じ日に同じ条件で同じ声で発声しても、文脈やそのときの気まぐれなどによって発音が異なってくる単語もあります。例えば、Tanzania は [Tanza-niya] と発音するのでしょうか、それとも [Tan-Zania] と発音するのでしょうか、それともその両方なのでしょうか? schedule は [sked-ule] でしょうか、それとも [shed-ule] でしょうか? status は [stay-tus] でしょうか、それとも [stattus] でしょうか?この問題に対しては、皆さん自身が一貫した発音をするように心がけることも、語彙目録にエントリーを追加することもできます。もう 1 つの微妙な問題として、音声録音の際に単語間の沈黙が長くなったり短くなったりする問題もあります。
  • モデルのベースは「音素」です。音素は音声認識プログラムが聞き取ることを想定している音声の離散表現です。音素は語彙目録に格納されますが、音素の精度が低い場合や一貫性がない場合、それらの音素に関連付けられる音声はエラーにつながる可能性があります。語彙目録内のエントリーで問題のある音素を特定して修正することで、「使われている単語が不適切な音素に分解されたり、代替となる発音が存在しなかったりする」などの問題を軽減することができます。
  • 残念ながら、多くの言語では通常の話し方の中に音素がバランスよく含まれているわけではないため、文法内の「音素バランスは不完全である」という結果になります。言語の中で音素が頻繁に使われないために音素を適切に表現することができないような場合には、音素について訓練する機会が限られているために、問題が発生する可能性があります。私が使用している英語の文法では、[ax]、[n]、[s]、[t] という音素の集合は頻繁に使われる傾向がある一方、[oy]、[ar]、[el]、[ur] という音素の集合は稀にしか使われません。この問題を解決するためには、まずは問題が持つ規則性を明らかにし、それから文法を調整し、同義語を使用して、HDMan の出力を見る、あるいは最も使用頻度の低い音素を使用する非標準的な用語を独自に作成するといったことすら行います。
  • 日常的な言葉のなかで、音声認識プログラムのレベルでは区別が困難な単語を使用せざるを得ないことによって、「語彙素の音素が非常によく似ている」という問題が発生します。このため文法のなかで使われるコマンドの選択には極めて繊細な配慮が必要になります。私が作成したモデルは時々 pipe と nine を混同します。これに対して私が作成したソリューションは、pipe を複合語の pipe_symbol または vertical_bar に変更するというものでした。

テスト手段

文法内のルールが数個しかなく、その 1 つが常に認識の問題を起こす場合には、極めて迅速にその問題にフォーカスすることができます。しかし文法が大規模な場合には、モデルの精度をシステマチックにテストする手段が必要です。

テスト手段 1: 問題のある単語を特定する

テスト手段 1 では、問題のある単語の組み合わせを特定し、後でそれらを検討して改善できるように保存するためのテスト・ルーチンを使用します。このルーチンを使用するメリットは、サンプルを全般的に追加するのではなく、音声認識プログラムにとって困難な文法ルールの例を追加することによって、時間を節約できることです。

ここでは、マシンはプロンプト・ファイルを読み取り、プロンプト・ファイルのエントリーを配列に格納してランダムにシャッフルし、それらのプロンプトを 1 つずつ画面上に表示するか、音声で出力することによって、皆さんがそのプロンプトを認識プログラムに対して声に出して読み上げるように指示します。次にスクリプトは想定していた聞こえ方と Julius の出力とを比較します。両者が同じではない場合、スクリプトは後で分析できるようにその問題を記録し、次のプロンプトへとループ処理で進みます。

このスクリプトは Python (「参考文献」を参照) を使用しており、PostgreSQL (「参考文献」を参照) でバックエンドに問題を格納します。同じことを PHP、Perl、MySQL、その他のスクリプトや DB を使用して行うこともできます。これらの選択にあたっては、作業環境に関していくつかのことを前提にしています。第 1 に、一連のテスト可能な文法ルールと、それに関連する音声ファイル名で構成された「プロンプト」のフラット・ファイルがあるものとします。音声ファイル名の後には、1 つまたは 2 つの単語によるプロンプトがあります。以下はその例です。このテスト手段では音声ファイル名を使用していないので、サンプル・プロンプトのみを含む別ファイルでも同じように機能します。

*/mysample1     ONE
*/mysample2     COMPUTER     QUIT

また、4 つのフィールドを持つ PostgreSQL テーブルを設定してあるものとします。この 4 つのフィールドは、レコードを一意にするための ID 番号、そしてそれぞれ異なる文字列を格納する 3 つのフィールドです。この 3 つのうち、第 1 のフィールドにはテストで使用する機器の情報を格納し、第 2 のフィールドにはコンピューターが要求したものを格納し、第 3 のフィールドには音声認識プログラムが聞いて解釈したものを格納します。リスト 1 で、testprom.py はスクリプトの名前であり、headset は使用しているヘッドセットの識別子、そして 100 はスクリプトが停止するまでにテストしたいプロンプトの数です。Julius の出力がパイプでスクリプトに入力されます。

リスト 1. Python と PostgreSQL を使用してプロンプトをテストする
#
# call with:
# julius (your options) -quiet -C julian.jconf | python testprom.py headset 100
#
import sys
import string as str
import os
import random
import psycopg2
# database setup
conn = psycopg2.connect("host='xxx' user='yyy' password='zzz' dbname='qqq'")
cur = conn.cursor()
# get the command line arguments
device = sys.argv[1]
limit = int(sys.argv[2]) # convert CLI string to integer
# get the prompts
proms = []
f = open('prompts', 'r')
for prompt in f:
  words = str.split(prompt)
  if len(words) == 2:
    thisprom = words[1]
  if len(words) == 3:
    thisprom = words[1]+' '+words[2]
  if thisprom not in proms:
    proms.append(thisprom)
f.close()
random.shuffle(proms)
# run the tests
i = 0
challengeprom = "please say something"
print challengeprom
while i < limit:
  line = sys.stdin.readline()
  if line[:4] == "sent":
    line = str.replace(line,"sentence1: <s> ","")
    line = str.replace(line," </s>","")
    heardprom = str.strip(line)
    if heardprom == challengeprom or i == 0:
      print "OK"
    else:
      sql = "insert into gramerrors (device, prompt, heard) values (%s,%s,%s)"
      data = (device,challengeprom,heardprom)
      cur.execute(sql, data)
      conn.commit()
      print "problem recorded: <"+challengeprom+'> <'+heardprom+'>'
    #
    challengeprom = proms[i]
    print challengeprom
    i += 1
# tidy up
cur.close()
conn.close()

リスト 1 (これは基本的に、非常に特殊なダイアログ・マネージャーです) ではまず、必要なライブラリーをインポートし、次に PostgreSQL バックエンドとの接続を確立してコマンドライン・パラメーターを読み取ります。続いて、スクリプトは読み取り用のプロンプト・ファイルを開き、1 行ずつ読み取り、リストに含まれていないプロンプトの場合にのみ、そのプロンプトをリストに格納します。読み取りを終えると、スクリプトはリストをランダムにシャッフルします。

次に、スクリプトはシャッフルされたプロンプトをリストの先頭からループ処理します。スクリプトは各プロンプトを画面に表示し (Festival (「参考文献」を参照) を使用して音声で出力することもできます)、音声認識プログラムからの出力を待ち (話者がマイクに向かって話すと音声認識プログラムからの出力がトリガーされます)、Julius は聞き取った内容をデコードします。するとスクリプトは、想定していたプロンプトと実際に聞き取ったプロンプトとを比較します。理想的な場合には、両者は同じであり、スクリプトは次のプロンプトに進みます。問題がある場合には、スクリプトは、問題が発生した機器、想定していたプロンプト、実際のプロンプトをバックエンドに記録してから次のプロンプトに進みます。限界 (つまりファイルの最後) まで達すると、スクリプトは停止します。

結果として作成されるテーブルには、問題の起きたプロンプトと、その問題を起こした機器の情報が含まれています。複数の機器を日常的に使用する場合、このテーブルが役立ちます。別の日に別の機器を使用してさらにテストを実行すると、どこで問題が発生するのかがわかるようになります。問題が発生するのは常に同じ機器でしょうか?同じプロンプトでしょうか?そして Julius は一貫して同じ不正確な文法ルールを出力するのでしょうか?それらを基に、詳細な分析を行うことができます。

テスト手段 2: 既存の音声サンプルを確認する

例えばテストの結果として、1 つのプロンプトで必ず問題が起きるとします。第 1 の疑わしい要素としては、そのプロンプトの 1 つまたは複数の音声ファイルが不適切で、交換が必要なのかもしれません。あるいは音声ファイルがプロンプトにマッチしていないのかもしれません。さらには音声レベルが過大で歪んでいるか、音声レベルが小さすぎるため、HTK が意味のあるデータをうまく抽出できないのかもしれません。必要な情報はプロンプト・ファイルの中にあります。リスト 2 のスクリプトは選択されたプロンプトをループ処理し、そのプロンプトの音声ファイルを再生します。

リスト 2. 音声ファイルを確認する
#
# called with "$ python audioreview.py word1 word2"
#
import sys
import string as str
import os
f = open('prompts', 'r')
path = './wavs/'
for line in f:
  words = str.split(line)
  if words[1] == sys.argv[1] and words[2] == sys.argv[2]:
    mywav = words[0] + '.wav'
    wavfil = path + mywav[2:]
    os.system("aplay " + wavfil)

このスクリプトはいくつかのモジュールをインポートし、プロンプト・ファイルを開いて読み取ります。次に、一度に 1 行ずつ読み取りながら、プロンプトの単語列とコマンドラインで指定された単語列とを比較します。両者が同じ場合には、スクリプトは関連付けされた音声ファイルを音声プレーヤー (この場合は aplay) へのシステム・コールを使用して再生します。スクリプトは各ファイルを再生するごとに一時停止します。この場合、皆さんは自分の耳を頼りに、音声ファイルの 1 つを置き換える必要があるかどうかを判断します。このスクリプトは手軽に音声を確認したい場合に便利なテスト手段となります。不適切な音声ファイルを見つけた場合には、通常の録音プロセスを使用して単純にその音声ファイルを置き換えます。

テスト手段 3: 語彙目録内の音素の選択を確認する

語彙目録は単語列とそれらの単語列を音素で表現したもののリストです。リスト 3 は語彙目録のエントリーの一例です。

リスト 3. 語彙目録のエントリーの一例
BLACK     [BLACK]     b l ae k
BLUE     [BLUE]     b l uw
BRAVO     [BRAVO]     b r aa v ow
BROWN     [BROWN]     b r aw n

この語彙目録の各行には 3 つのフィールドがあります。つまり単語 (語彙素)、その単語を視覚的に出力した場合の表現 (書記素)、そして発音された単語を表すために順番に並べられた音素の集合があります。ここでは 3 つのフィールドを 5 つの空白で区切っています。重要な点として、この区切り文字のおかげでリスト 4split メソッドが最後のフィールドの音素を 1 つの単位として保持できることに注意してください。最後のフィールドは 1 つの空白で音素を区切っているため、フィールドの区切りは 5 つの空白で判断する必要があります。

1 つの単語を確認するためには、リスト 4 のようなスクリプトを使用します。

リスト 4. 語彙目録の 1 つの単語の音素を確認する
#
# called with "$ python scanlex.py word"
#
import sys
import string as str
f = open('lextest', 'r')
for line in f:
  words = str.split(line," "*5)
  if words[0] == sys.argv[1]:
    print words

このスクリプトではまず、必要なモジュールをインポートし、語彙目録ファイルを開いて読み取ります。そしてファイルを 1 行ずつ読み取ってエントリーを視覚的に出力しますが、出力されるエントリーの最初のフィールドはコマンドラインの呼び出しで指定される検索対象の単語と同じです。指定された任意の単語に対して複数のエントリーが出力され、それぞれのエントリーが異なる音素で表現されている場合があることに注意してください。この語彙目録はアルファベット順にソートされているので、最後の「単語」を読み取った後でファイルの処理を停止するコードを追加しても、まったく構いません。私は最近、seven という単語で問題を経験しました。少しだけ非標準的な音素で表現したエントリー [s eh v ih n] を語彙目録に追加すると、その問題は完全に解消しました。

テスト手段 4: 文法内の語彙目録の選択が適切かどうかを確認する

テスト手段 4 はテスト手段 3 の拡張であり、プロンプト・リストをスキャンし、文法内の各単語に対する語彙目録の音素表現を視覚的に出力します。また、Festival を実行して単語の表現を問い合わせます。こうすることで、語彙目録の音素表現がほぼ適切かどうかを示す指針とすることができます。リスト 5 はそのためのコードです。

リスト 5. 文法内の単語に対して語彙目録が適切かどうかを確認する
#
# called with "$ python checkphons.py"
#
import sys
import string as str
import os
with open('prompts', 'r') as f:
  wdlist = []
  for line in f:
    words = str.split(line)
    if words[1] not in wdlist:
      wdlist.append(words[1])
    if words[2] not in wdlist:
      wdlist.append(words[2])
with open('lextest', 'r') as f:
  lexwd = []
  lexphon = []
  for line in f:
    lexs = str.split(line,' '*5)
    lexwd.append(str.strip(lexs[0]))
    lexphon.append(str.strip(lexs[2]))
for wd in wdlist:
  #print wd
  if wd in lexwd:
    pos = lexwd.index(wd)
    print wd, lexphon[pos]
    cmd = ("/home/colin/downloads/festival/bin/festival -b \
	      '(format t \"%l\\n\" (lex.lookup \""+wd+"\") ) '")
    os.system(cmd)

テスト手段 4 はプロンプト・ファイルを読み取り、単語をリストに格納します。次に語彙目録内の単語と音素に対して同じことを行います。最終ステージでは、プロンプトの単語リストをスキャンし、その単語の音素表現を語彙目録から出力します。各単語に対して Festival を呼び出し、その単語をどう発音すべきかの比較として、Festival で実行した結果を視覚的に出力します。このスクリプトをリスト 3 の最小限の語彙目録に対して実行した場合の出力例を以下に記載します。

BLACK b l ae k
("black" nil (((b l ae k) 1)))
BLUE b l uw
("blue" nil (((b l uw) 1)))
BROWN b r aw n
("brown" nil (((b r aw n) 1)))
BRAVO b r aa v ow
("bravo" nil (((b r aa v) 1) ((ow) 0)))

音素を検証する場合、HTK の HDMan ツールを使用すると、文法内で使用されている音素の概略の数が得られることに注意してください。これは VoxForge チュートリアルで生成される出力の一部です。大規模な文法の場合には、テスト手段 3 と同じ方法でフィルタリングすることができます。


まとめ

この記事で紹介したテスト手段を使用すると、理想的で静かな条件下で私が行った実験から見る限り、1000 回に 1 回という認識失敗率は至って妥当な目標のように思えます。文法と語彙目録に深刻な欠陥がない限り、多くの異なるソースから得られる、より多くのサンプル・プロンプトを単純に追加することで、モデルの問題点を少なくすることができます。モデルの作成に使用する単語や音声ファイルに少し注意を払い、注意深くモデルを作成すれば、上記の目標をもっと早く実現できるかもしれません。その結果、より満足の行く成果が得られ、より効率的な方法で機器とやり取りできるようになり、よりリラックスして発声できるようになります。

参考文献

学ぶために

製品や技術を入手するために

  • 隠れマルコフ・モデルを作成し、操作するための移植の容易なツールキット、HTK (Hidden Markov Model Toolkit) について学んでください。
  • Carnegie Mellon 大学の CMU Sphinx 音声合成ツールキットについて学んでください。
  • Julius 音声認識エンジンについて学んでください。
  • Festival 音声合成エンジンについて学んでください。
  • PostgreSQL リレーショナル・データベース・システムについて理解してください。
  • 皆さんに最適な方法で IBM 製品を評価してください。製品の試用版をダウンロードする方法、オンラインで製品を試す方法、クラウド環境で製品を使う方法、あるいは SOA Sandbox で数時間を費やし、サービス指向アーキテクチャーの効率的な実装方法を学ぶ方法などがあります。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=824412
ArticleTitle=音響モデルを調整し、音声認識の精度を高める
publish-date=07122012