はじめに
音声を聞こえたまま文字化したいという需要があります。前回はカナASR(自動音声認識)を試してみましたが、今回は音素レベルでの認識を試してみたいと思います。
音素ASRは単語や文字ではなく、音素(phoneme)という音の最小単位で音声を認識する手法です。これにより、より細かい音の違いを捉えることができる可能性があります。
今回は以下の2つのモデルを試してみます:
- Allosaurus: 多言語音素認識モデル
- wav2vec2-lv-60-espeak-cv-ft: Facebook製のeSpeak音素認識専用モデル
実装
Allosaurusによる音素認識
# scripts/recognize_by_allosaurus.py
import sys
from allosaurus.app import read_recognizer
if len(sys.argv) < 2:
print("Usage: python recognize_by_allosaurus.py <audio_file_path>")
sys.exit(1)
path = sys.argv[1]
# Allosaurusモデルをロード
model = read_recognizer()
# 音素認識実行
phonemes = model.recognize(path)
print("Recognized phonemes:", phonemes)
wav2vec2-lv-60-espeak-cv-ftによる音素認識
# scripts/recognize_by_wav2vec2phoneme.py
import torch
import librosa
from transformers import Wav2Vec2ForCTC, Wav2Vec2FeatureExtractor, Wav2Vec2CTCTokenizer
# モデルロード
MODEL_ID = "facebook/wav2vec2-lv-60-espeak-cv-ft"
model = Wav2Vec2ForCTC.from_pretrained(MODEL_ID)
feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained(MODEL_ID)
tokenizer = Wav2Vec2CTCTokenizer.from_pretrained(MODEL_ID)
# 音声読み込み(16kHz, mono)
wav, sr = librosa.load(path, sr=16000, mono=True)
# 音素認識処理
input_values = feature_extractor(wav, return_tensors="pt", sampling_rate=16000).input_values
with torch.no_grad():
logits = model(input_values).logits
predicted_ids = torch.argmax(logits, dim=-1)
phonemes_list = tokenizer.batch_decode(predicted_ids)
phonemes = phonemes_list[0] if phonemes_list else ""
print("Recognized phonemes:", phonemes)
結果
テストデータ
無意味日本語をVoiceVOXで合成した音声ファイルを使用してテストを行いました。
リファレンスと前回の要領でカナASRした結果は以下です。音声ファイルは実際に聞いたところ、リファレンス通りに聞こえるように合成できていました。
- リファレンス: ポークスルトオキジョーピージッコクラクデキワンジューガウトラクサレナ
- カナASR: トオクスルトオキジオピージッコクラクレキワンジュウガウトラクサレナ
Allosaurusの結果
リファレンス、認識結果音素列、音素列を目視でカナ変換した結果は以下です。
- リファレンス: ポークスルトオキジョーピージッコクラクデキワンジューガウトラクサレナ",
- 音素: pʰ o s ɪ l ə t a t i j i a o t i e p o pʰ ʌ ɡ ɒ f ʌ l e n t i w a n tʰ i ŋ t a t ʌ o l a k s ɒ n a
- 目視でカナ変換: ポシラタティジャオティエポパグォファレンティワンティングタタオラクサナ
「ポークスル <-> ポシラ」「トオキジョーピー <-> タティジャオティ」など全体的に精度があまり高くない印象でした。
wav2vec2-lv-60-espeak-cv-ftの結果
- リファレンス: ポークスルトオキジョーピージッコクラクデキワンジューガウトラクサレナ
- poksyɾɯtɔkidʒelkidʒikokurakuɾekivandʒiɡatoraksanena
- 目視でカナ変換: ポクスルタキジェルキジコクラクデキヴァンジガトラクサネナ
「ポークスル <-> ポクスル」「トオキジョーピー <-> タキジェルキ」など納得感のある認識結果が多く、少なくともallosaurusに比べるとかなり良いです。ただカナASRの結果に劣っているように思われます。
音素→カナ変換の課題
日本人利用者を想定すると、最終的にはカタカナでの出力が望ましいです。
しかし、音素(IPA)からカタカナに簡単に精度よく自動変換する方法は軽く調べた範囲ではなさそうでした。
試しに、IPAからARPABETを経由してカタカナに変換してみましたが、変換精度はあまり高くありませんでした。
uv add ipapy e2k
# Python 3.12対応のためのモンキーパッチ
import sys
import collections.abc
# ipapyライブラリが古いcollectionsインポートを使用している問題を解決
sys.modules['collections'].MutableMapping = collections.abc.MutableMapping
sys.modules['collections'].MutableSequence = collections.abc.MutableSequence
from ipapy.arpabetmapper import ARPABETMapper
from e2k import P2K
p2k = P2K()
def ipa_to_kana(ipa_str: str, ignore_unmapped: bool = True) -> list[str]:
"""
IPA → ARPAbet(リスト形式)変換(ipapy を使う方法)
未対応の文字を無視するオプションあり。
例: "pɹuːf" → ["P", "R", "UW", "F"]
"""
mapper = ARPABETMapper()
# return_as_list=True にすると、分割されたリストを得られる
result = mapper.map_unicode_string(ipa_str, ignore=ignore_unmapped, return_as_list=True)
katakana = p2k(result)
return katakana
この関数で、先程のwav2vec2の認識結果を変換してみると以下のようになります。
- ipa(入力): poksyɾɯtɔkidʒelkidʒikokurakuɾekivandʒiɡatoraksanena
- arpabet: ['P', 'OH', 'K', 'S', 'DX', 'T', 'AO', 'K', 'IH', 'JH', 'EH', 'L', 'K', 'IH', 'JH', 'IH', 'K', 'OH', 'K', 'UW', 'R', 'AE', 'K', 'UW', 'DX', 'EH', 'K', 'IH', 'V', 'AE', 'N', 'JH', 'IH', 'G', 'AE', 'T', 'OH', 'R', 'AE', 'K', 'S', 'AE', 'N', 'EH', 'N', 'AE']
- kana: プストックスクリング
ipa -> aprabetの変換はある程度うまく行ってそうですが、arpabet -> カナの変換はいまいちです。
短い単語で試しても同様の結果でした。
おそらくp2kは英語を入力言語と想定して学習されているため、今回のような日本語っぽい音素列にはあまり適していないのだと思われます。
おわりに
音素ASRを試してみました。
ごく少数のサンプル音声でしか試せていませんが、最終的にカナを得るという目的のもとでは、今のところはカナASRに軍配があがりそうです。