初めに
G2Pライブラリの一つであるニューラルG2Pモデル CharsiuG2P で日本語および英語の精度を測ってみます
特徴
提供モデル
| モデル |
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
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)
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():
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 = {}
results["cpu"] = run_test("cpu")
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 |
✅ ほぼ正確 |