Demo
学習したモデルは以下で公開しています
生成した音声は以下のようになります
開発環境
環境の構築
まずは学習環境の作成をしていきます。
まずはライブラリをcloneします
git clone https://github.com/rhasspy/piper.git
cd piper/src/python
python以外の環境を準備します
sudo apt-get update sudo apt-get install -y build-essential sudo apt-get install -y python3-dev espeak-ng
次に環境を作成します。今回は uvを使ってpythonの環境の仮想環境を作ります
cd src/python uv venv -p 3.11 source .venv/bin/activate
必要なライブラリをインストールします。
uv pip install --upgrade pip wheel setuptools uv pip install -e . uv pip install pytorch-lightning uv pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 ./build_monotonic_align.sh
データセットの準備
リポジトリがあるルートフォルダと同じところで、データセットのフォルダを作ってデータセットをダウンロードしていきます
mkdir -p ~/datasets cd ~/datasets wget https://data.keithito.com/data/speech/LJSpeech-1.1.tar.bz2 # 解凍 tar -xjvf LJSpeech-1.1.tar.bz2
このデータセットのパスを環境パスに保存しておきます
export INPUT_DATASET_DIR="your path/datasets/LJSpeech-1.1"
前処理・ログ用のフォルダの作成
前処理済みデータと学習ログを保存するディレクトリを作成します。
export TRAINING_DATA_DIR="your path/piper_ljspeech_training" mkdir -p $TRAINING_DATA_DIR
前処理の実行
先ほど設定した環境変数を使い データセットの前処理を行っていきます
python3 -m piper_train.preprocess \ --language en-us \ --input-dir ${INPUT_DATASET_DIR} \ --output-dir ${TRAINING_DATA_DIR} \ --dataset-format ljspeech \ --single-speaker \ --sample-rate 22050
だいたい1時間くらいでした(スペックによります)
完了すると以下のようなログになります
ファイルによっては途中で失敗することがあります。その際には以下のような 処理済みのファイルから未処理のデータセット一覧を作成して前処理のみを再開することができます
import csv import os import json import sys from tqdm import tqdm def create_resume_file(original_metadata_path, preprocessed_dir_path, resume_output_path): """ 前処理が中断した箇所から再開するための新しいメタデータファイルを作成します。 """ # 1. 既に処理済みのファイルのリストを作成する processed_audio_paths = set() partial_dataset_jsonl = os.path.join(preprocessed_dir_path, "dataset.jsonl") if not os.path.exists(partial_dataset_jsonl): print(f"Warning: Partial 'dataset.jsonl' not found at {partial_dataset_jsonl}. Assuming no files were processed.") else: print(f"Reading already processed files from {partial_dataset_jsonl}...") with open(partial_dataset_jsonl, 'r', encoding='utf-8') as f: for line in f: try: data = json.loads(line) if "audio_path" in data: processed_audio_paths.add(data["audio_path"]) except json.JSONDecodeError: print(f"Warning: Could not decode JSON line: {line.strip()}", file=sys.stderr) print(f"Found {len(processed_audio_paths)} processed files.") # 2. 元のメタデータを読み込み、未処理の行だけを新しいファイルに書き出す print(f"Reading original metadata from {original_metadata_path} to find unprocessed files...") unprocessed_rows = [] original_dataset_dir = os.path.dirname(original_metadata_path) with open(original_metadata_path, 'r', encoding='utf-8') as f: reader = csv.reader(f, delimiter='|') all_rows = list(reader) for row in tqdm(all_rows, desc="Comparing metadata"): if not row or len(row) < 1: continue file_id = row[0] # dataset.jsonlに記録されているフルパスと一致させる # piper_train.preprocess は入力ディレクトリからの相対パスではなく、絶対パスを記録することがあるため、 # os.path.join で結合してフルパスを作成します。 expected_audio_path = os.path.join(original_dataset_dir, "wavs", f"{file_id}.wav") if expected_audio_path not in processed_audio_paths: unprocessed_rows.append(row) print(f"Found {len(unprocessed_rows)} unprocessed files.") # 3. 未処理の行を新しいメタデータファイルに保存 if unprocessed_rows: print(f"Saving resume metadata to {resume_output_path}...") with open(resume_output_path, 'w', encoding='utf-8', newline='') as f: writer = csv.writer(f, delimiter='|') writer.writerows(unprocessed_rows) print("Resume file created successfully.") else: print("No unprocessed files found. Preprocessing may have completed or an error occurred before any processing.") if __name__ == "__main__": # --- ★設定箇所★ --- # Piper学習用に準備した元のデータセットのディレクトリ # (`metadata.csv` と `wavs/` がある場所) original_dataset_dir = "/data/moe-speech-plus-ljspeech" # お客様が --input-dir で指定したパス # 前処理が中断した出力先ディレクトリ # (中に部分的な `dataset.jsonl` がある場所) preprocessed_dir = "/data/piper_moe-speech-plus_preprocessed_single" # お客様が --output-dir で指定したパス # --- 設定ここまで --- original_metadata = os.path.join(original_dataset_dir, "metadata.csv") resume_metadata = os.path.join(original_dataset_dir, "metadata_resume.csv") # 新しく作成するファイル create_resume_file(original_metadata, preprocessed_dir, resume_metadata)
これを実行することで、metadata_resume.csv が作成されます。
このファイルをmetadata.csvに名前を変更して再度前処理を実行します
INFO:preprocess:Single speaker dataset INFO:preprocess:Wrote dataset config INFO:preprocess:Processing 13100 utterance(s) with 48 worker(s)
事前学習の開始
以下のコマンドにて事前学習を行います
python -m piper_train \ --dataset-dir ${TRAINING_DATA_DIR} \ --accelerator 'gpu' \ --devices 1 \ --batch-size 24 \ --validation-split 0.05 \ --num-test-examples 10 \ --max_epochs 5000 \ --checkpoint-epochs 1 \ --precision 32 \ --quality medium
調べた感じマルチGPUは対応していないみたいだったので、仕方なくシングルGPUで学習を行っています
学習されたモデルは以下のパスに保存されています
piper_ljspeech_training/lightning_logs/version_X/checkpoints/
学習後のlossは以下のようになりました

モデルをonnxに変換
これを推論するためにonnxに変換します
python3 -m piper_train.export_onnx \ /data/piper_ljspeech_training/lightning_logs/version_1/checkpoints/epoch=499-step=519000.ckpt \ ~/piper_MODELS_EXPORTED/my_ljspeech_piper_voice.onnx
この時にモデルの設定ファイルもコピーしておきます
cp /data/piper_ljspeech_training/config.json \ ~/piper_MODELS_EXPORTED/my_ljspeech_piper_voice.onnx.json
学習したモデルから推論
onnxに変換したモデルから実際に音声を生成してみます 英語のみの音声データのため、英語のテキストを使って音声合成を行います。
echo "Hello, this is a test of the trained Piper model." | \ piper \ -m ./${ONNX_MODEL_NAME}.onnx \ --output_file output.wav