100言語での音韻変換に対応しているCharsiuG2Pで日本語精度を・英語精度を試す

初めに

G2Pライブラリの一つであるニューラルG2Pモデル CharsiuG2P で日本語および英語の精度を測ってみます

特徴

項目 内容
ライブラリ CharsiuG2P
モデル charsiu/g2p_multilingual_byT5_small_100
ライセンス MIT
対応言語 100言語(日本語含む)
アーキテクチャ byT5 (Byte-level T5)

提供モデル

モデル PER WER
tiny 8-layer 0.107 0.314
tiny 12-layer 0.098 0.287
tiny 16-layer 0.096 0.281
small (今回検証) 0.089 0.261

開発環境

環境構築

以下でuvで環境を作っていきます

uv init

次に 必要なライブラリを入れます

uv add transformers torch --index-url https://download.pytorch.org/whl/cu124

精度比較

次に英語と日本語の両方の測度と精度を測るためのスクリプトを作成します

"""CharsiuG2P G2P検証スクリプト(日本語・英語)"""

import sys
import io

# Windows環境でのUTF-8出力対応
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

from transformers import T5ForConditionalGeneration, AutoTokenizer
import torch
import time


def test_language(model, tokenizer, lang_code, lang_name, test_words, device):
    """指定言語でG2P変換テストを実行"""
    print(f"\n[{lang_name}] G2P変換テスト")
    print("-" * 60)

    total_time = 0
    results = []

    for word, expected in test_words:
        input_text = f"<{lang_code}>: {word}"
        inputs = tokenizer(input_text, return_tensors="pt").to(device)

        # ウォームアップ(最初の1回はスキップ)
        if len(results) == 0 and device.type == "cuda":
            _ = model.generate(**inputs, max_length=50)
            torch.cuda.synchronize()

        start = time.time()
        outputs = model.generate(**inputs, max_length=50)
        if device.type == "cuda":
            torch.cuda.synchronize()
        inference_time = time.time() - start
        total_time += inference_time

        phonemes = tokenizer.decode(outputs[0], skip_special_tokens=True)
        results.append((word, phonemes, expected, inference_time))

        print(f"入力: {word}")
        print(f"  出力: {phonemes}")
        print(f"  期待: {expected}")
        print(f"  時間: {inference_time*1000:.1f}ms")
        print()

    avg_time = (total_time / len(test_words)) * 1000
    throughput = len(test_words) / total_time

    print("-" * 60)
    print(f"平均推論時間: {avg_time:.1f}ms/語")
    print(f"スループット: {throughput:.1f}語/秒")

    return results, avg_time, throughput


def run_test(device_name="cpu"):
    """指定デバイスでテストを実行"""
    device = torch.device(device_name)

    print("=" * 60)
    print(f"CharsiuG2P G2P検証(日本語・英語)- {device_name.upper()}")
    print("=" * 60)

    # モデルとトークナイザーのロード
    print("\n[1] モデルをロード中...")
    start = time.time()

    model_name = "charsiu/g2p_multilingual_byT5_small_100"
    tokenizer = AutoTokenizer.from_pretrained("google/byt5-small")
    model = T5ForConditionalGeneration.from_pretrained(model_name).to(device)

    load_time = time.time() - start
    print(f"    ロード完了: {load_time:.2f}秒")
    print(f"    デバイス: {device}")

    # モデル情報
    param_count = sum(p.numel() for p in model.parameters())
    print(f"    パラメータ数: {param_count:,}")

    # ========== 英語テスト ==========
    english_words = [
        ("hello", "hɛˈloʊ"),
        ("world", "wɜːld"),
        ("computer", "kəmˈpjuːtər"),
        ("language", "ˈlæŋɡwɪdʒ"),
        ("phoneme", "ˈfoʊniːm"),
        ("Tokyo", "ˈtoʊkioʊ"),
        ("Microsoft", "ˈmaɪkroʊsɒft"),
        ("Google", "ˈɡuːɡəl"),
        ("psychology", "saɪˈkɒlədʒi"),
        ("knight", "naɪt"),
        ("through", "θruː"),
        ("enough", "ɪˈnʌf"),
    ]

    en_results, en_avg_time, en_throughput = test_language(
        model, tokenizer, "eng-us", "英語 (eng-us)", english_words, device
    )

    # ========== 日本語テスト ==========
    japanese_words = [
        ("東京", "toːkjoː"),
        ("大阪", "oːsaka"),
        ("京都", "kjoːto"),
        ("日本", "nihoɴ / nippoɴ"),
        ("こんにちは", "konnitɕiwa"),
        ("ありがとう", "ariɡatoː"),
        ("コンピュータ", "kompjuːta"),
        ("インターネット", "intaːnetto"),
        ("人工知能", "dʑinkoːtɕinoː"),
        ("機械学習", "kikaiɡakuɕɯː"),
        ("渋谷", "ɕibuja"),
        ("秋葉原", "akihabara"),
    ]

    ja_results, ja_avg_time, ja_throughput = test_language(
        model, tokenizer, "jpn", "日本語 (jpn)", japanese_words, device
    )

    return {
        "device": device_name,
        "en_avg_time": en_avg_time,
        "en_throughput": en_throughput,
        "ja_avg_time": ja_avg_time,
        "ja_throughput": ja_throughput,
        "en_results": en_results,
        "ja_results": ja_results,
        "param_count": param_count,
    }


def main():
    # GPU情報表示
    print("=" * 60)
    print("システム情報")
    print("=" * 60)
    print(f"PyTorch: {torch.__version__}")
    print(f"CUDA available: {torch.cuda.is_available()}")
    if torch.cuda.is_available():
        print(f"CUDA version: {torch.version.cuda}")
        print(f"GPU: {torch.cuda.get_device_name(0)}")
    print()

    results = {}

    # CPUテスト
    results["cpu"] = run_test("cpu")

    # GPUテスト(利用可能な場合)
    if torch.cuda.is_available():
        print("\n\n")
        results["cuda"] = run_test("cuda")

    # ========== サマリー ==========
    print("\n\n" + "=" * 60)
    print("検証サマリー")
    print("=" * 60)

    for device, r in results.items():
        print(f"\n【{device.upper()}】")
        print(f"  英語:")
        print(f"    平均推論時間: {r['en_avg_time']:.1f}ms/語")
        print(f"    スループット: {r['en_throughput']:.1f}語/秒")
        print(f"  日本語:")
        print(f"    平均推論時間: {r['ja_avg_time']:.1f}ms/語")
        print(f"    スループット: {r['ja_throughput']:.1f}語/秒")

    # 速度比較
    if "cuda" in results:
        print("\n【速度比較 (CPU vs GPU)】")
        cpu_en = results["cpu"]["en_avg_time"]
        gpu_en = results["cuda"]["en_avg_time"]
        cpu_ja = results["cpu"]["ja_avg_time"]
        gpu_ja = results["cuda"]["ja_avg_time"]
        print(f"  英語: {cpu_en:.1f}ms → {gpu_en:.1f}ms ({cpu_en/gpu_en:.1f}x 高速化)")
        print(f"  日本語: {cpu_ja:.1f}ms → {gpu_ja:.1f}ms ({cpu_ja/gpu_ja:.1f}x 高速化)")

    print("\n" + "=" * 60)
    print("検証完了")
    print("=" * 60)

    # 結果をファイルに保存
    with open("results.txt", "w", encoding="utf-8") as f:
        f.write("CharsiuG2P G2P検証結果(日本語・英語、CPU/GPU比較)\n")
        f.write("=" * 60 + "\n\n")

        f.write(f"PyTorch: {torch.__version__}\n")
        if torch.cuda.is_available():
            f.write(f"GPU: {torch.cuda.get_device_name(0)}\n")
        f.write(f"パラメータ数: {results['cpu']['param_count']:,}\n\n")

        for device, r in results.items():
            f.write(f"{'=' * 60}\n")
            f.write(f"【{device.upper()}】\n")
            f.write(f"{'=' * 60}\n\n")

            f.write("英語 (eng-us) 変換結果:\n")
            f.write("-" * 60 + "\n")
            for word, phonemes, expected, t in r["en_results"]:
                f.write(f"入力: {word}\n")
                f.write(f"  出力: {phonemes}\n")
                f.write(f"  期待: {expected}\n")
                f.write(f"  時間: {t*1000:.1f}ms\n\n")
            f.write(f"平均推論時間: {r['en_avg_time']:.1f}ms/語\n")
            f.write(f"スループット: {r['en_throughput']:.1f}語/秒\n\n")

            f.write("日本語 (jpn) 変換結果:\n")
            f.write("-" * 60 + "\n")
            for word, phonemes, expected, t in r["ja_results"]:
                f.write(f"入力: {word}\n")
                f.write(f"  出力: {phonemes}\n")
                f.write(f"  期待: {expected}\n")
                f.write(f"  時間: {t*1000:.1f}ms\n\n")
            f.write(f"平均推論時間: {r['ja_avg_time']:.1f}ms/語\n")
            f.write(f"スループット: {r['ja_throughput']:.1f}語/秒\n\n")

        if "cuda" in results:
            f.write("=" * 60 + "\n")
            f.write("速度比較 (CPU vs GPU)\n")
            f.write("=" * 60 + "\n")
            cpu_en = results["cpu"]["en_avg_time"]
            gpu_en = results["cuda"]["en_avg_time"]
            cpu_ja = results["cpu"]["ja_avg_time"]
            gpu_ja = results["cuda"]["ja_avg_time"]
            f.write(f"英語: {cpu_en:.1f}ms → {gpu_en:.1f}ms ({cpu_en/gpu_en:.1f}x 高速化)\n")
            f.write(f"日本語: {cpu_ja:.1f}ms → {gpu_ja:.1f}ms ({cpu_ja/gpu_ja:.1f}x 高速化)\n")

    print("\n結果をresults.txtに保存しました")


if __name__ == "__main__":
    main()

速度測定結果

速度はCPU、GPUでそれぞれ以下のようになりました

項目 英語 日本語
推論速度 958 ms/語 857 ms/語
スループット 1.0 語/秒 1.2 語/秒

GPU (RTX 4070 Ti SUPER)

項目 英語 日本語
推論速度 175 ms/語 167 ms/語
スループット 5.7 語/秒 6.0 語/秒

精度比較

精度評価(英語)

入力 出力 期待値 評価
hello ˈhɛɫoʊ hɛˈloʊ ✅ ほぼ正確
world ˈwɝɫd wɜːld ✅ ほぼ正確
computer kəmˈpjutɝ kəmˈpjuːtər ✅ ほぼ正確
language ˈɫæŋɡwɪdʒ ˈlæŋɡwɪdʒ
phoneme ˈfoʊnməpə ˈfoʊniːm ❌ 余計な文字
Tokyo ˈtoʊkiˌoʊ ˈtoʊkioʊ
Microsoft ˈmaɪkɹoʊˌsɔft ˈmaɪkroʊsɒft ✅ ほぼ正確
Google ˈɡuɡəɫ ˈɡuːɡəl ✅ ほぼ正確
psychology saɪˈkɑɫədʒi saɪˈkɒlədʒi ✅ ほぼ正確
knight ˈnaɪt naɪt
through ˈθɹaʊ θruː ❌ 誤変換
enough ɪˈnəf ɪˈnʌf ✅ ほぼ正確

精度評価(日本語)

入力 出力 期待値 評価
東京 toɯkjoɯ toːkjoː △ 長音がɯに
大阪 oosakazɯki oːsaka ❌ 余計な文字
京都 kjoɯtoɯ kjoːto △ 長音がɯに
日本 nihoɴ nihoɴ
こんにちは koɴnitɕiha konnitɕiwa △ ha/waの違い
ありがとう aɾigatoɯ ariɡatoː △ 長音がɯに
コンピュータ koɴpjɯːta kompjuːta ✅ ほぼ正確
インターネット iɴtaːnetːo intaːnetto ✅ ほぼ正確
人工知能 dʑiɴkoɯtɕinoɯ dʑinkoːtɕinoː △ 長音がɯに
機械学習 kikaigakɯɕɯɯ kikaiɡakuɕɯː
渋谷 ɕibɯtani ɕibuja ❌ 誤読
秋葉原 akibahaɾa akihabara ✅ ほぼ正確