HTSで話者適応。少量サンプルで音響モデル作成! 本編

Ubuntu18.04にて、HTSを使って話者適応の音響モデル(.htsvoice)を作るシリーズの第3回です。話者適応モデルは、複数話者の音声データから「平均声」をつくって、それに新たに作成したい話者の音声データの特徴量をのせることで音響モデルを作成する方法になります。少ない音声データで新たな音響モデルが作成できることが特徴です。

再びこちらのサイトを大いに参考にさせていただきました。

https://ragolun.exblog.jp/23144146/

  

4. カスタム音響モデルの作成

いよいよ本題であるカスタム音響モデルです。

<音声の録音>

<平均声用>
友人3人に頼んでATR503文を読み上げていただきました。
録音環境1:Macbook12インチのボイスメモアプリ USBマイク 自室
録音環境2:Windowsのデスクトップで「SoundEngine Free」を使用 高性能USBマイク
録音環境3:WindowsノートPCで「SoundEngine Free」を使用 高性能USBマイク
ボイスメモアプリだと.m4aで保存されるため、これを「MediaHuman Audio Converter」で.wavに変換(16bit, Mono, 48000Hz)。
「SoundEngine Free」だと直接wav形式の細かな指定ができるのでそのまま使用できました。
そのままでは音量が少し足りなかったため「audacity」を使ってノーマライズしました。データ量が多いのでなかなか骨が折れます(読み上げてくれた友人達に心から感謝)。
最後に、ファイル名を”a01.wav”〜”a50.wav”、”b01.wav”〜”b50.wav”…と変更します。

<適応用>
適応させたい音声はYoutubeの動画からとってきました。Youtubeからmp3に変換し、さらにwavに変換します。そのデータから「audacity」を使っていい感じの部分を切り出します(今回は20個分くらい)。

  

<音素ラベリング>

サンプルでは必要ありませんでしたが、カスタム音声になると音素のラベリングが必要になります。ATR503文を読み上げていただいたものには、「segment_atr503_windows-v1.0」の方を使い、Youtubeからとってきたものには「segment_adapt_windows-v1.0」の方を使います。

〜ATR503文を読み上げたもの〜

1. 音声データの格納

”HTS/segment_atr503_windows-v1.0”直下に発話者の名前など任意の名前をつけたフォルダをつくり、その中に503個の音声データを入れます。

2. 実行ファイルを修正

”HTS/segment_atr503_windows-v1.0”の中の、”segment_atr503.pl”を開きます。(エディタは任意。)

4行目: $speaker = “akihiro”; を、 $speaker = “(自分のつけたフォルダ名)”
8行目: $julius4bin=”./bin/julius-4.2.1.exe”; を、 $julius4bin=”julius”;
78行目: push(words, $_); を、 push(@words, $_);

というように修正し、保存します。もとのファイルのバックアップを取っておくと安心です。

3. 端末を開き、以下のコマンドを実行

~$cd HTS/segment_atr503_windows-v1.0
~/HTS/segment_atr503_windows-v1.0 $ perl segment_atr503.pl

4. 出力の確認(full, mono, raw)

”HTS/segment_atr503_windows-v1.0”直下に任意の名前で作成したフォルダを見てみると、格納した音声データ以外に、”full”、”mono”、”raw”の3つのフォルダが出現しているはずです。それぞれ中身を確認して、ファイルがきちんとあれば、成功です。

  

〜Youtubeからとってきたもの〜

1. オリジナルのfull,monoラベルの作成

ATR503文以外の発話内容からラベルを作るのは、上記のようなプロセスにもうひと手間かかります。それは、ラベルを作るためのラベルを作る、という作業です。
詳しく説明すると、先程の「segment_atr503_windows-v1.0」では、発話内容がATR503文と決まっているため、すでにATR503文用のラベルの元ネタが入っていたのです。このラベルの元ネタは、声の種類はテキトーで、発話内容に音素ラベリングがされたものです。「segment_atr503_windows-v1.0」では元ネタではテキトーだった声の種類を音声データに合わせた声に変えて、音素ラベルを作り直す、という工程を踏んでいるのです。
つまり結局ラベルの元ネタがないと「segment_atr503_windows-v1.0」は動かない、ということになります。ではこのラベルの元ネタですが、どのように作るのでしょうか?
こちらに答えがありました(先人達に心からの感謝…)。

そこで、OpenJtalkのログを使ったやりかたを今回はやってみたいと思います。ちなみにラベルの2つのフォルダ名monoとfullの意味ですが、monoは単音(monophone)での音素ラベリング、fullは文脈を加味したフルコンテクスト(fullcontext)での音素ラベリングとのことだそうです。(最初、OpenJtalkではなくJuliusのセグメンテーションキットを使った音素セグメンテーションに挑んだのですが、monophoneか、それに少し毛が生えたtriphoneのラベリングはできたものの、fullcontextがどうしてもつくれず、詰みました。)

OpenJtalkをどのように使うかを最初に言っておくと、通常どおりに音声を生成するときにログファイルを出力するオプションをつける、というだけです。このログファイルの中に、なんと求めていたfullcontextのラベルデータが入っているのです。音声を生成&ログを出力するコマンドは以下の通りです。「こんにちは」の部分は、Youtubeでとってきた発話内容に置き換えます。

~$ echo 'こんにちは' | open_jtalk -x /var/lib/mecab/dic/open-jtalk/naist-jdic -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -r 1.0 -ow test.wav -ot open_jtalk.log

このログファイルの中にある、[Output label]の項目(数字や%,xxなどが並んでいる部分)がfullcontextのラベルファイルとして使えます。この部分をコピーして、新規作成したファイルに貼り付け、.labの拡張子をつけて保存したらfullラベルのできあがりです。

いちいち実行&コピペするのは面倒だったので、Pythonプログラムで一部自動化しました。コードは以下のとおりです。結局音声ファイルからの最終文字起こしは手作業ですが…(Google Speech to Textに7割ほど負担してもらっています)。一つのテキストファイルに書いたものを一行ずつ読み込ませています。変数名とか適当です。文字列の抽出条件は”0000”かつ”xx/”を含むという形でいけました。

#!/usr/bin/env python3
#text_log.py
import subprocess
filepath = '{path_to_your_text_folder}'
filename = '{your_text_file_name}'
filefull = filepath + filename
f = open(filefull, mode='r')
f_lines = f.readlines()
num = 1
for line in f_lines:
   print(line.strip())
   word = line.strip()
   cmd1 = 'echo '
   cmd2 = ' | open_jtalk -x /var/lib/mecab/dic/open-jtalk/naist-jdic -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -r 1.0 -ow output.wav -ot open_jtalk.log'
   subprocess.run(cmd1 + word + cmd2, shell=True)
   log = open('open_jtalk.log', 'r')
   j_lines = log.readlines()
   log.close()
   full_filename = 'full_make/full' + str(num) +'.lab'
   full = open(full_filename, 'a')
   for line in j_lines:
      if line.find('0000') >= 0 and line.find('xx/') >= 0:
         full.write(line)
   full.close()
   num += 1

つづいてmonoラベルをつくっていきます。上述したJuliusによるmonoラベルが使えるのかと思ったら、どうやらfullと内容が対応していないとダメみたいで、うまくいきませんでした。そこで、先程OpenJtalkのログファイルから作成したfullラベルから、無理やりmonoラベルをつくってしまいたいと思います。

文字列を抽出するPythonプログラムを作成し、半自動でmonoラベルを生成しました。作成したコードは以下のとおりです。

#!/usr/bin/env python3
#full2mono.py
for i in range(1,{number_of_your_audiofiles}):
   fp_full = '{path_to_your_full_folder}'
   fp_mono = '{path_to_your_mono_folder}'
   fn = 'a%02.f.lab' % i
   with open(fp_full + fn, mode='rt', encoding='utf-8') as f:
      for line in f:
         mono = []
         words = line.split(' ')
         for word in words:
            if '+' in word:
               ws1 = word.split('+')[0]
               ws2 = ws1.split('-')[1]
               mono.append(ws2)
            else:
               mono.append(word)
         mono_str = ' '.join(map(str, mono))
         ff = open(fp_mono + fn, 'a')
         ff.write(mono_str + '\n')
         ff.close()

これで、無事にfull,monoのラベルの元ネタができました。適当に”full”と”mono”のフォルダをつくって、これらを入れておきます。

2. 音声データ&ラベルデータの格納

”HTS/segment_adapt_windows-v1.0”直下に発話者の名前など任意の名前をつけたフォルダをつくり、直下に用意した音声データを入れます。さらにフォルダ内に”labels”というフォルダを作成し、その中に先ほど作成したラベルの元ネタの”mono”と”full”のフォルダを入れます。

3. 実行ファイルを修正

”HTS/segment_adapt_windows-v1.0”の中の、”segment_adapt.pl”を開きます。(エディタは任意。)

4行目: $speaker = “akihiro”; を、 $speaker = “(自分のつけたフォルダ名)”
8行目: $julius4bin=”./bin/julius-4.2.1.exe”; を、 $julius4bin=”julius”;
77行目: push(words, $_); を、 push(@words, $_);
[追記]
176行目: 「system(“echo $speechfile | sox -t wav $speechfile -t raw -L -2 -s $speaker/raw/${filename}.raw”);」 を、「system(“echo $speechfile | sox $speechfile $speaker/data/raw/${filename}.raw”);」に。

というように修正し、保存します。もとのファイルのバックアップを取っておくと安心です。

4. 端末を開き、以下のコマンドを実行

~$cd HTS/segment_adapt_windows-v1.0
~/HTS/segment_adapt_windows-v1.0 $ perl segment_adapt.pl

5. 出力の確認(full, mono, raw)

”HTS/segment_adapt_windows-v1.0”直下に任意の名前で作成したフォルダを見てみると、格納した音声データおよび”labels”フォルダ以外に、”data”というフォルダが出現しており、その中に”full”、”mono”、”raw”の3つのフォルダがあるはずです。それぞれ中身を確認して、ファイルがきちんとあれば、成功です(ファイルの大きさが0バイトだった場合は失敗ですので、何らかの変更が必要です)。

  

<カスタム音響モデル>

1. Makefile.inファイルの修正

基本的に”HTS-demo_CMU-ARCTIC-ADAPT”は、英語仕様であるので、日本語に対応させる修正が必要です。
”HTS/HTS-demo_CMU-ARCTIC-ADAPT/data”の中にある”Makefile.in”ファイルをメインで変更していきます。(エディタは任意。)

まず、uttに関する部分をコメントアウトします(#を行頭につける)。uttは英語の場合は必要ですが、日本語では不必要な要素のようです。検索を活用してuttが関係するところをまるごとコメントアウトします。自分の場合、294(if [ $(USEUTT) -eq 1 ]; then \)〜323行目(fi; \)をコメントアウトしました。

次にfullとmonoのラベルを勝手に消去してしまう部分をコメントアウトします。自分の場合は434,435行目でした。「rm -rf labels/mono」および「rm -rf labels/full」と書いてあるところです。

2. Htsvoice出力の日本語対応化

前述しましたがHTS-demo_CMU-ARCTIC-ADAPTは英語仕様であるので、そのまま実行すると、完成したHtsvoiceファイルがOpenJtalkに非対応になってしまいます(英語版のHtsvoiceファイルとして出力される)。そのため、この設定を変更する必要があります。変更の仕方はネットで調べても出てこなかったので、ソースコードを見ながら試行錯誤でやりました。ですので正しいかは不明ですが、修正後はOpenJtalkできちんと出力できたので、おそらく大丈夫なはずです。

<configureファイルの修正>
”HTS/HTS-demo_CMU-ARCTIC-ADAPT”の中にある”configure”ファイルを開き、そのなかの「FULLCONTEXT_FORMAT=HTS_TTS_ENG」のところを、「FULLCONTEXT_FORMAT=HTS_TTS_JPN」に変えて、保存します。自分の場合4292行目でした。

<Config.pm.inファイルの修正>
”HTS/HTS-demo_CMU-ARCTIC-ADAPT/scripts”の中にある”Config.pm.in”ファイルを開き、「# silent and pause phoneme(141行目)」のところで、「@slnt = (‘pau’,’h#’,’brth’);」を「@slnt = (‘pau’,’sil’);」に修正します。さらに、そのすぐ下の「# model copy(143行目)」のところで、%mdcpについているコメントアウト(#)を6行すべて外します。

以上の修正で、出力されるHtsvoiceファイルが、OpenJtalkで出力可能になります。

3. full, mono, rawの移動

<1人目>
”HTS/segment_adapt_windows-v1.0”直下に任意の名前で作成したフォルダに出現した”labels/full”フォルダの中身を、”HTS/HTS-demo_CMU-ARCTIC-ADAPT/data/labels/full/bdl”の中身と置き換えます。
同様に”labels/mono”フォルダの中身も”HTS/HTS-demo_NIT-ATR503-M001/data/labels/mono/bdl”の中身と置き換えます。
最後に”labels/raw”フォルダは、”HTS/HTS-demo_NIT-ATR503-M001/data/raw/bdl”の中身と置き換えます。
そして、各ファイルの名前すべてを「cmu_us_arctic_bdl_(もとのファイル名:例a01)」という形に統一します(拡張子はそれぞれ.lab,.lab,.raw)。

<2人目>
”HTS/segment_adapt_windows-v1.0”直下に任意の名前で作成したフォルダに出現した”labels/full”フォルダの中身を、”HTS/HTS-demo_CMU-ARCTIC-ADAPT/data/labels/full/clb”の中身と置き換えます。
同様に”labels/mono”フォルダの中身も”HTS/HTS-demo_NIT-ATR503-M001/data/labels/mono/clb”の中身と置き換えます。
最後に”labels/raw”フォルダは、”HTS/HTS-demo_NIT-ATR503-M001/data/raw/clb”の中身と置き換えます。
そして、各ファイルの名前すべてを「cmu_us_arctic_clb_(もとのファイル名:例a01)」という形に統一します(拡張子はそれぞれ.lab,.lab,.raw)。

<3人目>
”HTS/segment_adapt_windows-v1.0”直下に任意の名前で作成したフォルダに出現した”labels/full”フォルダの中身を、”HTS/HTS-demo_CMU-ARCTIC-ADAPT/data/labels/full/jmk”の中身と置き換えます。
同様に”labels/mono”フォルダの中身も”HTS/HTS-demo_NIT-ATR503-M001/data/labels/mono/jmk”の中身と置き換えます。
最後に”labels/raw”フォルダは、”HTS/HTS-demo_NIT-ATR503-M001/data/raw/jmk”の中身と置き換えます。
そして、各ファイルの名前すべてを「cmu_us_arctic_jmk_(もとのファイル名:例a01)」という形に統一します(拡張子はそれぞれ.lab,.lab,.raw)。

<4人目> … 今回適用させたい人の声
”HTS/segment_adapt_windows-v1.0”直下に任意の名前で作成したフォルダに出現した”labels/full”フォルダの中身を、”HTS/HTS-demo_CMU-ARCTIC-ADAPT/data/labels/full/slt”の中身と置き換えます。
同様に”labels/mono”フォルダの中身も”HTS/HTS-demo_NIT-ATR503-M001/data/labels/mono/slt”の中身と置き換えます。
最後に”labels/raw”フォルダは、”HTS/HTS-demo_NIT-ATR503-M001/data/raw/slt”の中身と置き換えます。
そして、各ファイルの名前すべてを「cmu_us_arctic_slt_(もとのファイル名:例a01)」という形に統一します(拡張子はそれぞれ.lab,.lab,.raw)。

4. 既存生成フォルダの削除

以前に実行していた場合、”HTS/HTS-demo_CMU-ARCTIC-ADAPT”の中にある、”configs”,”edfiles”,”gen”,”gv”,”models”,”proto”,”stats”,”trees”,”voices”の9つのフォルダをすべて削除します。さらに、”HTS/HTS-demo_CMU-ARCTIC-ADAPT/data”の中にある”bap”,”lf0”,”mgc”,”scp”,”labels”,”cmp”の6つのフォルダも削除します。仮にこれらが残ったまま実行すると、以前の実行の際に作成されたものを使ってしまう可能性があり、音声が混ざることがあります。

5. configureファイルの編集

学習させる音声たちの基本周波数を考慮して、抽出する周波数の範囲を定めます。”HTS/HTS-demo_CMU-ARCTIC-ADAPT”にある”configure”というファイルを開き、「F0_RANGES」を定義している行を編集します(自分の場合は4492行目)。男声の基本周波数は120〜130Hz、女声の基本周波数は220〜270Hzほどだそうです。ですので、これをカバーする感じで定義すればいいと思われます。自分は男声なら 60 200、女声なら 100 400 という感じで編集しました。

6. 端末を開き、以下のコマンドを実行。

~$cd HTS/HTS-demo_CMU-ARCTIC-ADAPT
~/HTS/HTS-demo_CMU-ARCTIC-ADAPT $ ./configure --with-fest-search-path=$HOME/HTS/festival/festival/examples --with-sptk-search-path=$HOME/HTS/prog/SPTK/bin --with-hts-search-path=$HOME/HTS/prog/htk/bin --with-hts-engine-search-path=$HOME/HTS/prog/hts_engine_API/bin
~/HTS/HTS-demo_CMU-ARCTIC-ADAPT $ make

7. 出力の確認

数時間待つ(半日程かかります)と、いよいよカスタム音響モデルの完成です。
”HTS-demo_CMU-ARCTIC-ADAPT/voices/qst001/ver1”にhtsvoiceファイルができていると思います。(ファイル名は”cmu_us_arctic_slt.htsvoice”)
”HTS-demo_CMU-ARCTIC-ADAPT/gen/qst001/ver1/SAT(およびSI)/0”には、平均声のサンプル音声(wavファイル)が入っています。
”HTS-demo_CMU-ARCTIC-ADAPT/gen/qst001/ver1/SAT(およびSI)+dec_feat3/0(およびhts_engine)”には、話者適応された声のサンプル音声がはいっています。(さすがに20文では十分な精度が得られず…)

Htsvoiceファイルができさえすれば、OpenJtalkなどを使って好きな言葉を喋らせることが可能です。(最初、こちらで生成されるHtsvoiceファイルがOpenJtalkで再生できず焦りましたが、上記の日本語対応化のための修正を施すと無事に再生できました。)

  

参考URL

ragolunさん https://ragolun.exblog.jp/23144146/
アキヒロさん http://akihiro0105.blog55.fc2.com/blog-entry-12.html
CUBE370さん http://cube370.blog87.fc2.com/blog-entry-55.html
マーチンさん http://mahoro-ba.net/e1876.html

  

話者適応モデルの音響モデル作成の流れは以上となります。長文に付き合ってくださり、ありがとうございました!

3件の返信

  1. 阿部高志 より:

    segment_adapt_windows-v1.0を使い、内容がATR503文以外の音声ファイルから作成したいのですが、いまいちfullとmonoファイルの作成手順がわかりません。
    open-jtalkを使いlogと音声ファイルの作成はできましたが、pythonファイルを使っての実行方法はどのようにするといいのでしょうか?

    フォルダーのパスなどはlogファイルのある場所、
    ファイルの名前は出力されたtxtファイルの名称ですよね?
    それらを入れ替えて
    $ python3 pythonファイル名
    コマンド実行で処理されると思ったのですが、インデントエラーやファイルはないよと言われてしまいます。

    お手数ですが、どこにどのパスやファイルを書けばいいか教えていただけないでしょうか?

    以下のコードはどうにか一行分だけは読み込んだfull用pythonファイルです。

    #!/usr/bin/env python3
    #text_log.py
    import subprocess
    filepath = ‘/home/ユーザー名/{filenameのある階層パス}’
    filename = ‘音声ファイルの読み上げテキスト.txt’
    filefull = filepath + filename
    f = open(filefull, mode=’r’)
    f_lines = f.readlines()
    num = 1
    for line in f_lines:
    print(line.strip())
    word = line.strip()
    cmd1 = ‘echo ‘
    cmd2 = ‘ | open_jtalk -x /var/lib/mecab/dic/open-jtalk/naist-jdic -m htsvoiceファイルのパス -r 1.0 -ow output.wav -ot open_jtalk.log’
    subprocess.run(cmd1 + word + cmd2, shell=True)
    log = open(‘open_jtalk.log’, ‘r’)
    j_lines = log.readlines()
    log.close()
    full_filename = ‘full_make/full’ + str(num) +’.lab’
    full = open(full_filename, ‘a’)
    for line in j_lines:
    if line.find(‘0000’) >= 0 and line.find(‘xx/’) >= 0:
    full.write(line)
    full.close()
    num += 1

    エラーは

    Traceback (most recent call last):
    File “text_log.py”, line 23, in
    full.write(line)
    ValueError: I/O operation on closed file.

    python自体をあまり理解していないので中身を理解できておらず申し訳ないです。

  2. 阿部高志 より:

    一行ずつ調べて調整した結果fullの出力に先ほど成功しました。
    最後のfull.close()がifやforの中に入れていたのが原因でした。

    monoのほうもどうにか形にできました。
    (不要な数値(同じ数値)が入っている気がするので若干記述間違いがありそうです
     monoのそれぞれの数値の後ろにあるアルファベット(ローマ字?)もありません)

    次にセグメントラベル云々を行おうとしたのですが、

    Start segmentation a01
    ERROR: Error in loading model
    sox FAIL formats: can’t open output file `girl_2/raw/a01.raw’: No such file or directory

    モデルのロード中にエラーが起きたと表示され、空(0バイト)のファイルだけが構築されます。

    原因としてはmonoファイルの不要らしき数値、若しくは音声ファイルの設定が違っていると考えていますが対処方法がわかりません。
    そもそもATR503文ではないのが原因なのでしょうか?
    ATR503文以外で作るようなので違うと思いますが。

    • kyabe2 より:

      ラベル作成用のPythonスクリプトが、不親切になっていて申し訳ありません。インデントの情報が消し飛んでましたね汗 wordpressだと行頭の空白が無視されることに気づいておらず…。記事の方を修正いたしました。
      さらに、エラーの原因ですが、大変申し訳ないことに追記した
      「system(“echo $speechfile | sox $speechfile $speaker/raw/${filename}.raw”)」
      のところが、
      「system(“echo $speechfile | sox $speechfile $speaker/data/raw/${filename}.raw”)」
      であるべきでした!ご迷惑をおかけしました。

コメントは受け付けていません。