はじめに
ARPAbetを日本語カナに変換するライブラリarpakanaを作ったので、解説します。
https://github.com/jiroshimaya/arpakana
使い方
ARPAbetのリストまたは空白区切りの文字列を渡すと、対応するカタカナ文字列が返ります。
uv add arpakana
from arpakana import arpabet_to_kana
print(arpabet_to_kana("HH AH0 L OW1")) # ハロウ
背景
ARPAbetは、英語の発音記号をASCII文字のみで表現するために設計された記法です。
ARPAbetをカタカナ表記へ変換する試みはこれまでにもいくつか存在し、主に次の2つのアプローチに分類できます。
- ルールベース手法
- ARPAbet を子音×母音の組み合わせ規則に基づいてカタカナへ変換し、さらに挿入母音(/u, o/ など)、撥音・促音、r 音化(ER など)といった特殊処理も行います。具体的なルールや実装例としては「英語をカタカナ表記に変換してみる」が参考になります。
- 機械学習ベース手法
- RNN などを用いて「音素列 → カタカナ」を直接学習するアプローチです。ARPAbet には日本語と対応させにくい曖昧母音が存在するため、このような手法に一定の意義があります。e2kでは ARPAbet をカタカナへ変換する機能が実装されています。
pip経由で使えるpythonライブラリは探した範囲では機械学習ベースのe2kだけでしたが、e2kのARPAbet–カナ変換機能が長めの入力に対して安定しにくそうでした。そこで、多くのケースではルールベースでも十分な性能が期待できることもあり、新たにルールベースのPythonライブラリを開発することにしました。実装にあたっては、上記の関連手法を参考にさせていただきました。
実装
arpakanaライブラリでは、ARPAbetを日本語のカナ文字へ変換するために、以下の手順で処理を行っています。
- 音素の正規化(大文字化、ストレス記号除去)
- ER/AXRの展開(AX R に分解)
- 母音の正規化
- 促音の挿入
- R音素の変換規則適用
- 子音+母音の組み合わせ変換
- 単独子音の変換
- 未知音素の置換
以下、各ステップの詳細を説明します。
音素の正規化
入力された音素を大文字化し、末尾のストレス番号(0, 1, 2)を削除します。例えば、"AH0" は "AH" に変換されます。
ER/AXRの展開
"ER" と "AXR" は "AX R" に分解されます。 これにより、後続のR音素変換規則が適用しやすくなります。
母音の正規化
母音音素をaiueoのいずれかに標準化します。変換規則は以下によります。二重母音や長音についてもこの時点で考慮します。
_VOWEL_MAP: dict[str, tuple[str, ...]] = {
"AA": ("a",),
"AE": ("a",),
"AH": ("a",),
"AO": ("o",),
"AW": ("a", "ウ"),
"AX": ("a",),
"AXR": ("a", "ー"), # 互換性維持(事前に "AX R" へ展開する想定)
"AY": ("a", "イ"),
"EH": ("e",),
"ER": ("a", "ー"), # 互換性維持(事前に "AX R" へ展開する想定)
"EY": ("e", "イ"),
"IH": ("i",),
"IX": ("i",),
"IY": ("i", "ー"),
"OW": ("o", "ウ"),
"OY": ("o", "イ"),
"OH": ("o", "ー"),
"UH": ("u",),
"UW": ("u", "ー"),
"UX": ("u",),
}
促音の挿入
特定の子音(CH、SHなど)の前が短母音の場合、「ッ」を挿入します。
例えば、"K Y AE CH" は "キャッチ" に変換されます。
長母音や子音に続く場合には促音は挿入されません。
例えば、"K Y ER CH" は "キャーチ" になります。
R音素の変換規則適用
"R" 音素に、直前の音素に応じて異なるカナを割り当てます。
基本は「ア」とします。(例: "B IH R" → "ビア")
直前が"a"または"o"の場合には「ー」とします。(例: "B AA R" → "バー")
直前が長音の場合は、空文字に変換します。「ー」の連続を避けるためです。
直後にaiueoが続く場合は「ラリルレロ」になります。
子音+母音の組み合わせ変換
R以外の子音と母音の組み合わせに対して、対応するカナを割り当てます。
単独子音の変換
残った子音を単独で変換します。例えば、"B" は "ブ" に変換されます。
未知音素の置換
変換できない音素に対しては、指定された置換文字列(デフォルトは空文字)に置き換えます。
変換事例
テストケースの一部を記載します。
from arpakana.arpabet import arpabet_to_kana
def test_正常系_基本単語() -> None:
# hello
assert arpabet_to_kana("HH AH0 L OW1") == "ハロウ"
# sky
assert arpabet_to_kana("S K AY") == "スカイ"
# blue
assert arpabet_to_kana(["B", "L", "UW"]) == "ブルー"
# train
assert arpabet_to_kana("T R EY N") == "トゥレイン"
# bout
assert arpabet_to_kana("B AW1 T") == "バウトゥ"
# 'cause
assert arpabet_to_kana("K AH0 Z") == "カズ"
# 'course
assert arpabet_to_kana("K AO1 R S") == "コース"
# 'm
assert arpabet_to_kana("AH0 M") == "アン"
# frisco
assert arpabet_to_kana("F R IH1 S K OW0") == "フリスコウ"
def test_正常系_複合子音() -> None:
# cues
assert arpabet_to_kana("K Y UW1 Z") == "キューズ"
# aquamarine
assert arpabet_to_kana("AA K W AH M ER IY N") == "アクワマリーン"
def test_正常系_TS音素() -> None:
# cats
assert arpabet_to_kana("K AE1 T S") == "カッツ"
# watches
assert arpabet_to_kana("W AA1 CH IH0 Z") == "ワッチズ"
# abducts
assert arpabet_to_kana("AE0 B D AH1 K T S") == "アブダクツ"
def test_正常系_NG音素() -> None:
# quote
assert arpabet_to_kana("K W OW1 T") == "クウォウトゥ"
# bengtson
assert arpabet_to_kana("B EH1 NG T S AH0 N") == "ベンツァン"
# fourthquarter
assert arpabet_to_kana("F AO1 R TH K W AO1 R T ER0") == "フォースクウォーター"
def test_正常系_R() -> None:
# amateurish
assert arpabet_to_kana("AE1 M AH0 CH ER2 IH0 SH") == "アマッチャリッシュ"
# ameliorate
assert arpabet_to_kana("AH0 M IY1 L Y ER0 EY2 T") == "アミーリャレイトゥ"
# bird
assert arpabet_to_kana("B ER1 D") == "バード"
# fear
assert arpabet_to_kana("F IH1 R") == "フィア"
# bear
assert arpabet_to_kana("B EH1 R") == "ベア"
# before
assert arpabet_to_kana("B IH0 F AO1 R") == "ビフォー"
# aboard
assert arpabet_to_kana("AH0 B AO1 R D") == "アボード"
# sure
assert arpabet_to_kana("SH UH1 R") == "シュア"
def test_正常系_未知トークン() -> None:
assert arpabet_to_kana("XYZ", unknown="*") == "*"
def test_促音挿入ルール() -> None:
# rich
assert arpabet_to_kana("R IH1 CH") == "リッチ"
# beach
assert arpabet_to_kana("B IY1 CH") == "ビーチ"
# fish
assert arpabet_to_kana("F IH1 SH") == "フィッシュ"
# marsh
assert arpabet_to_kana("M AA1 R SH") == "マーシュ"
# boots
assert arpabet_to_kana("B UW1 T S") == "ブーツ"