Docker環境でJETSモデルの音声合成および学習を行う

初めに

TTSのモデルの中にjetsがあります。2年ほど前のモデルなので環境構築がかなり大変だったのでDockerを使って動かせる環境を作ります

今回の記事に関する内容は以下のリポジトリにまとめています。

github.com

開発環境

  • Windows11

必要なリポジトリをclone

jetsを動かすためには、以下のライブラリが必要になります。 * espnet * kaldi

そのため以下のような構造になるようにリポジトリをcloneしていきます。 それぞれのリポジトリは、今回のために整理したリポジトリを作成しました。

project/
├── espnet/
   ├── tools
      ├── kaldi

Docker環境を作る

jetsの当時の環境が安定していたた、以下の条件でdocker環境を構築します

  • cuda 11.x
  • python 3.8
  • torch 1.10

以下が実際に作成したDockerfileです

FROM nvidia/cuda:11.3.1-cudnn8-runtime-ubuntu20.04

# 非対話モードとタイムゾーンの設定
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Tokyo

# tzdata を先にインストールしてタイムゾーンの設定、その後必要なパッケージをインストール
RUN apt-get update && \
    apt-get install -y tzdata && \
    ln -fs /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
    dpkg-reconfigure --frontend noninteractive tzdata && \
    apt-get install -y software-properties-common && \
    add-apt-repository universe && \
    apt-get update && \
    apt-get install -y git \
                       python3.8 python3.8-dev python3.8-venv python3-pip wget \
                       libfreetype6-dev libpng-dev pkg-config && \
    python3.8 -m pip install --upgrade pip

# PyTorch 1.10.1 (CUDA 11.3 対応版) のインストール
RUN python3.8 -m pip install torch==1.10.1+cu113 torchvision==0.11.2+cu113 torchaudio==0.10.1+cu113 \
    -f https://download.pytorch.org/whl/torch_stable.html

# python コマンドで python3.8 を利用できるようにシンボリックリンクを作成
RUN ln -sf /usr/bin/python3.8 /usr/bin/python

WORKDIR /work/espnet

ENV PYTHONPATH=/work/espnet:$PYTHONPATH

# ここで ESPnet の依存ライブラリをインストール
RUN python -m pip install \
    kaldiio==2.18.0 \
    humanfriendly==10.0 \
    numpy==1.24.4 \
    resampy==0.4.3 \
    soundfile==0.13.1 \
    nltk==3.9.1 \
    tqdm==4.67.1 \
    matplotlib==3.7.5 \
    typeguard==2.7.1 \
    inflect==5.0.3 \
    espnet_model_zoo==0.1.7

# 必要な NLTK リソースのダウンロード(同じ Python 環境内で実行)
RUN python -c "import nltk; nltk.download('averaged_perceptron_tagger_eng')"

CMD ["/bin/bash"]

この時点でのフォルダ構造は以下になります

project/
├── espnet/
│  ├── tools
│     ├── kaldi
├──Dockerfile

このDockerfileを以下でビルドして、コンテナ内に入ります。このときにボリュームはマウントして実行します

docker build -t espnet-jets .
docker run -it --rm -v "${PWD}:/work" espnet-jets bash

前処理

コンテナ内に入った後にespnetが正しくインストールされるように以下を実行してインストールを行います

pip install -e .

音声合成の実行

まずは実行するためのフォルダを移動します

cd egs2/ljspeech/tts1

次に音声合成を行うために以下を実行します。 データの前処理、モデルのダウンロードを一括で行います (少し時間がかかるので放置してください)

./run.sh --skip_data_prep false --skip_train true --download_model imdanboy/jets

音声合成の結果

以下のパスに処理が終わった後にwavファイルが保存されています。

exp/imdanboy/jets/decode_train.loss.ave/dev/wav/
root@a895d360c83b:/work/espnet/egs2/ljspeech/tts1# ls -l exp/imdanboy/jets/decode_train.loss.ave/dev/wav/
total 69404
-rw-r--r-- 1 root root 235052 Feb 17 15:21 LJ049-0008.wav
-rw-r--r-- 1 root root 421420 Feb 17 15:22 LJ049-0009.wav
-rw-r--r-- 1 root root 138284 Feb 17 15:22 LJ049-0010.wav
-rw-r--r-- 1 root root 394284 Feb 17 15:22 LJ049-0011.wav

以下は推論実行時のログです

root@a895d360c83b:/work/espnet/egs2/ljspeech/tts1# ./run.sh --skip_data_prep false --skip_train true --download_model imdanboy/jets
2025-02-17T15:11:01 (tts.sh:211:main) ./tts.sh --lang en --feats_type raw --fs 22050 --n_fft 1024 --n_shift 256 --token_type phn --cleaner tacotron --g2p g2p_en_no_space --train_config conf/train.yaml --inference_config conf/decode.yaml --train_set tr_no_dev --valid_set dev --test_sets dev eval1 --srctexts data/tr_no_dev/text --audio_format wav --skip_data_prep false --skip_train true --download_model imdanboy/jets
2025-02-17T15:11:02 (tts.sh:307:main) Stage 1: Data preparation for data/tr_no_dev, data/dev, etc.
2025-02-17T15:11:02 (data.sh:16:main) local/data.sh 
2025-02-17T15:11:02 (data.sh:39:main) stage -1: Data Download
already exists. skipped.
2025-02-17T15:11:03 (data.sh:44:main) stage 0: Data Preparation
utils/validate_data_dir.sh: WARNING: you have only one speaker.  This probably a bad idea.
   Search for the word 'bold' in http://kaldi-asr.org/doc/data_prep.html
   for more information.
utils/validate_data_dir.sh: Successfully validated data-directory data/train
2025-02-17T15:20:07 (data.sh:77:main) stage 2: utils/subset_data_dir.sg
utils/subset_data_dir.sh: reducing #utt from 13100 to 500
utils/subset_data_dir.sh: reducing #utt from 500 to 250
utils/subset_data_dir.sh: reducing #utt from 500 to 250
utils/subset_data_dir.sh: reducing #utt from 13100 to 12600
2025-02-17T15:20:10 (data.sh:86:main) Successfully finished. [elapsed=548s]
2025-02-17T15:20:10 (tts.sh:323:main) Stage 2: Format wav.scp: data/ -> dump/raw/
utils/copy_data_dir.sh: copied data from data/tr_no_dev to dump/raw/org/tr_no_dev
utils/validate_data_dir.sh: WARNING: you have only one speaker.  This probably a bad idea.
   Search for the word 'bold' in http://kaldi-asr.org/doc/data_prep.html
   for more information.
utils/validate_data_dir.sh: Successfully validated data-directory dump/raw/org/tr_no_dev
2025-02-17T15:20:12 (format_wav_scp.sh:42:main) scripts/audio/format_wav_scp.sh --nj 8 --cmd run.pl --audio-format wav --fs 22050 data/tr_no_dev/wav.scp dump/raw/org/tr_no_dev
2025-02-17T15:20:13 (format_wav_scp.sh:110:main) [info]: without segments
2025-02-17T15:20:34 (format_wav_scp.sh:142:main) Successfully finished. [elapsed=22s]
utils/copy_data_dir.sh: copied data from data/dev to dump/raw/org/dev
utils/validate_data_dir.sh: WARNING: you have only one speaker.  This probably a bad idea.
   Search for the word 'bold' in http://kaldi-asr.org/doc/data_prep.html
   for more information.
utils/validate_data_dir.sh: Successfully validated data-directory dump/raw/org/dev
2025-02-17T15:20:35 (format_wav_scp.sh:42:main) scripts/audio/format_wav_scp.sh --nj 8 --cmd run.pl --audio-format wav --fs 22050 data/dev/wav.scp dump/raw/org/dev
2025-02-17T15:20:36 (format_wav_scp.sh:110:main) [info]: without segments
2025-02-17T15:20:39 (format_wav_scp.sh:142:main) Successfully finished. [elapsed=4s]
utils/copy_data_dir.sh: copied data from data/dev to dump/raw/org/dev
utils/validate_data_dir.sh: WARNING: you have only one speaker.  This probably a bad idea.
   Search for the word 'bold' in http://kaldi-asr.org/doc/data_prep.html
   for more information.
utils/validate_data_dir.sh: Successfully validated data-directory dump/raw/org/dev
2025-02-17T15:20:40 (format_wav_scp.sh:42:main) scripts/audio/format_wav_scp.sh --nj 8 --cmd run.pl --audio-format wav --fs 22050 data/dev/wav.scp dump/raw/org/dev
2025-02-17T15:20:41 (format_wav_scp.sh:110:main) [info]: without segments
2025-02-17T15:20:44 (format_wav_scp.sh:142:main) Successfully finished. [elapsed=4s]
utils/copy_data_dir.sh: copied data from data/eval1 to dump/raw/eval1
utils/validate_data_dir.sh: WARNING: you have only one speaker.  This probably a bad idea.
   Search for the word 'bold' in http://kaldi-asr.org/doc/data_prep.html
   for more information.
utils/validate_data_dir.sh: Successfully validated data-directory dump/raw/eval1
2025-02-17T15:20:45 (format_wav_scp.sh:42:main) scripts/audio/format_wav_scp.sh --nj 8 --cmd run.pl --audio-format wav --fs 22050 data/eval1/wav.scp dump/raw/eval1
2025-02-17T15:20:45 (format_wav_scp.sh:110:main) [info]: without segments
2025-02-17T15:20:49 (format_wav_scp.sh:142:main) Successfully finished. [elapsed=4s]
2025-02-17T15:20:49 (tts.sh:468:main) Stage 3: Remove long/short data: dump/raw/org -> dump/raw
utils/copy_data_dir.sh: copied data from dump/raw/org/tr_no_dev to dump/raw/tr_no_dev
utils/validate_data_dir.sh: WARNING: you have only one speaker.  This probably a bad idea.
   Search for the word 'bold' in http://kaldi-asr.org/doc/data_prep.html
   for more information.
utils/validate_data_dir.sh: Successfully validated data-directory dump/raw/tr_no_dev
fix_data_dir.sh: kept all 12600 utterances.
fix_data_dir.sh: old files are kept in dump/raw/tr_no_dev/.backup
utils/copy_data_dir.sh: copied data from dump/raw/org/dev to dump/raw/dev
utils/validate_data_dir.sh: WARNING: you have only one speaker.  This probably a bad idea.
   Search for the word 'bold' in http://kaldi-asr.org/doc/data_prep.html
   for more information.
utils/validate_data_dir.sh: Successfully validated data-directory dump/raw/dev
fix_data_dir.sh: kept all 250 utterances.
fix_data_dir.sh: old files are kept in dump/raw/dev/.backup
2025-02-17T15:20:55 (tts.sh:523:main) Stage 4: Generate token_list from data/tr_no_dev/text
[nltk_data] Downloading package averaged_perceptron_tagger to
[nltk_data]     /root/nltk_data...
[nltk_data]   Unzipping taggers/averaged_perceptron_tagger.zip.
[nltk_data] Downloading package cmudict to /root/nltk_data...
[nltk_data]   Unzipping corpora/cmudict.zip.
/usr/bin/python3 /work/espnet/espnet2/bin/tokenize_text.py --token_type phn -f 2- --input dump/raw/srctexts --output dump/token_list/phn_tacotron_g2p_en_no_space/tokens.txt --non_linguistic_symbols none --cleaner tacotron --g2p g2p_en_no_space --write_vocabulary true --add_symbol '<blank>:0' --add_symbol '<unk>:1' --add_symbol '<sos/eos>:-1'
2025-02-17 15:21:10,513 (tokenize_text:174) INFO: OOV rate = 0.0 %
2025-02-17T15:21:10 (tts.sh:907:main) Skip training stages
2025-02-17T15:21:10 (tts.sh:912:main) Use imdanboy/jets for decoding and evaluation
()2p_en_no_space%2Ftrain%2Fpitch_stats.npz: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 770/770 [00:00<00:00, 266kB/s]
()e%2Fimages%2Fdiscriminator_fake_loss.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 75.2k/75.2k [00:00<00:00, 4.79MB/s]
()p_en_no_space%2Ftrain%2Fenergy_stats.npz: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 770/770 [00:00<00:00, 238kB/s]
()images%2Fdiscriminator_backward_time.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 73.7k/73.7k [00:00<00:00, 5.66MB/s]
README.md: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11.6k/11.6k [00:00<00:00, 4.72MB/s]
()n_tacotron_g2p_en_no_space%2Fconfig.yaml: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 9.54k/9.54k [00:00<00:00, 3.15MB/s]
()2p_en_no_space%2Ftrain%2Ffeats_stats.npz: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1.40k/1.40k [00:00<00:00, 481kB/s]
.gitattributes: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1.17k/1.17k [00:00<00:00, 426kB/s]
()ages%2Fdiscriminator_optim_step_time.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 36.0k/36.0k [00:00<00:00, 11.1MB/s]
()_space%2Fimages%2Fdiscriminator_loss.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 75.1k/75.1k [00:00<00:00, 16.8MB/s]
()%2Fimages%2Fdiscriminator_train_time.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 74.1k/74.1k [00:00<00:00, 19.7MB/s]
()es%2Fgenerator_align_forwardsum_loss.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 32.7k/32.7k [00:00<00:00, 26.1MB/s]
()%2Fimages%2Fgenerator_align_bin_loss.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 32.8k/32.8k [00:00<00:00, 23.1MB/s]
()pace%2Fimages%2Fgenerator_align_loss.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 31.7k/31.7k [00:00<00:00, 23.5MB/s]
()e%2Fimages%2Fgenerator_backward_time.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 76.1k/76.1k [00:00<00:00, 29.8MB/s]
()ce%2Fimages%2Fgenerator_forward_time.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 27.8k/27.8k [00:00<00:00, 25.9MB/s]
()Fimages%2Fdiscriminator_forward_time.png: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 76.3k/76.3k [00:00<00:00, 533kB/s]
()e%2Fimages%2Fdiscriminator_real_loss.png: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 75.7k/75.7k [00:00<00:00, 505kB/s]
()pace%2Fimages%2Fgenerator_g_adv_loss.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 65.5k/65.5k [00:00<00:00, 45.3MB/s]
()no_space%2Fimages%2Fgenerator_g_loss.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 36.8k/36.8k [00:00<00:00, 40.3MB/s]
()pace%2Fimages%2Fgenerator_g_mel_loss.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 33.9k/33.9k [00:00<00:00, 28.1MB/s]
()images%2Fgenerator_g_feat_match_loss.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44.5k/44.5k [00:00<00:00, 21.5MB/s]
()n_no_space%2Fimages%2Fgenerator_loss.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 33.2k/33.2k [00:00<00:00, 28.5MB/s]
()2Fimages%2Fgenerator_optim_step_time.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 39.0k/39.0k [00:00<00:00, 35.3MB/s]
()2Fimages%2Fgenerator_var_energy_loss.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 31.1k/31.1k [00:00<00:00, 27.1MB/s]
()ce%2Fimages%2Fgenerator_var_dur_loss.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 39.5k/39.5k [00:00<00:00, 30.8MB/s]
()ace%2Fimages%2Fgpu_max_cached_mem_GB.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 32.8k/32.8k [00:00<00:00, 22.4MB/s]
()_space%2Fimages%2Fgenerator_var_loss.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 30.9k/30.9k [00:00<00:00, 10.4MB/s]
()%2Fimages%2Fgenerator_var_pitch_loss.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 35.4k/35.4k [00:00<00:00, 14.0MB/s]
()pace%2Fimages%2Fgenerator_train_time.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 30.3k/30.3k [00:00<00:00, 33.5MB/s]
()2p_en_no_space%2Fimages%2Foptim0_lr0.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 25.2k/25.2k [00:00<00:00, 22.6MB/s]
()2p_en_no_space%2Fimages%2Foptim1_lr0.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 25.0k/25.0k [00:00<00:00, 19.9MB/s]
()2p_en_no_space%2Fimages%2Ftrain_time.png: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 36.2k/36.2k [00:00<00:00, 27.2MB/s]
()g2p_en_no_space%2Fimages%2Fiter_time.png: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 85.4k/85.4k [00:00<00:00, 592kB/s]
meta.yaml: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 326/326 [00:00<00:00, 319kB/s]
train.total_count.ave_5best.pth: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 334M/334M [00:09<00:00, 33.6MB/s]
Fetching 36 files: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 36/36 [00:12<00:00,  2.90it/s]
2025-02-17T15:21:24 (tts.sh:933:main) Stage 7: Decoding: training_dir=exp/imdanboy/jets
2025-02-17T15:21:24 (tts.sh:956:main) Generate 'exp/imdanboy/jets/decode_train.loss.ave/run.sh'. You can resume the process from stage 7 using this script
2025-02-17T15:21:25 (tts.sh:1010:main) Decoding started... log: 'exp/imdanboy/jets/decode_train.loss.ave/dev/log/tts_inference.*.log'

2025-02-17T15:30:33 (tts.sh:1010:main) Decoding started... log: 'exp/imdanboy/jets/decode_train.loss.ave/eval1/log/tts_inference.*.log'

2025-02-17T15:40:02 (tts.sh:1180:main) Skip the uploading stage
2025-02-17T15:40:02 (tts.sh:1232:main) Skip the uploading to HuggingFace stage
2025-02-17T15:40:02 (tts.sh:1235:main) Successfully finished. [elapsed=1741s]

JETSの学習

学習をする場合は、以下のコマンドにて一から学習を開始できます

./run.sh --train_config conf/tuning/train_jets.yaml --tts_task gan_tts --stage 1 --stop_stage 7 --ngpu 1

レーニングのパラメータを引数から変更したい場合は、以下を付けます

--train_args "--max_epoch 1 --num_iters_per_epoch 30"