19 KiB
19 KiB
transliteration_transliterator.py - 総合音写・転写システム
概要
SudachiPyを利用した日本語のローマ字転写システムのメインクラスです。形態素解析、漢字・送り仮名の分離、文脈依存ルールの適用、ヘボン式変換を統合し、高精度な日本語ローマ字化を提供します。
主要機能
統合転写システム
- SudachiPyによる高精度形態素解析
- 漢字・送り仮名の自動分離処理
- 文脈依存読み変更ルールの適用
多層変換処理
- カタカナ読み取得・分配
- ひらがな自動変換
- ヘボン式ローマ字生成
並行処理対応
- スレッドセーフなトークナイザー利用
- ロック機構による安全な並行実行
- 高負荷環境での安定動作
クラス構造
Transliterator クラス
class Transliterator:
def __init__(self) -> None:
self.tokenizer_obj: tokenizer.Tokenizer
self.mode: tokenizer.Tokenizer.SplitMode
self._tokenizer_lock: threading.Lock
日本語転写処理の中核クラス
属性
- tokenizer_obj: SudachiPyトークナイザーインスタンス
- mode: 分割モード(SplitMode.C = 最長一致)
- _tokenizer_lock: 並行アクセス制御用ミューテックス
主要メソッド
analyze
def analyze(self, text: str, use_macron: bool = False) -> List[Dict[str, Any]]
テキストを解析して転写情報を生成
パラメータ
- text: 解析対象の日本語テキスト
- use_macron: マクロン使用フラグ(長音表記方式)
戻り値
- List[Dict[str, Any]]: トークン転写情報のリスト
出力辞書構造
{
"orig": str, # 元の文字・文字列
"kana": str, # カタカナ読み
"hira": str, # ひらがな読み
"hepburn": str # ヘボン式ローマ字
}
split_kanji_okurigana (静的メソッド)
@staticmethod
def split_kanji_okurigana(surface: str, reading_kana: str, use_macron: bool = True) -> List[Dict[str, str]]
単語の表層形と読みを漢字・送り仮名ブロックに分割
パラメータ
- surface: 表層形(漢字+ひらがな混在可能)
- reading_kana: 全体のカタカナ読み
- use_macron: ヘボン式変換でのマクロン使用
戻り値
- List[Dict[str, str]]: 分割された部分の転写情報
補助メソッド
is_kanji (静的メソッド)
@staticmethod
def is_kanji(ch: str) -> bool
文字が漢字かどうかを判定
パラメータ
- ch: 判定対象文字
戻り値
- bool: 漢字判定結果
kata_to_hira (静的メソッド)
@staticmethod
def kata_to_hira(text: str) -> str
カタカナをひらがなに変換
パラメータ
- text: 変換対象のカタカナテキスト
戻り値
- str: ひらがな変換結果
使用方法
基本的な転写処理
from models.transliteration.transliteration_transliterator import Transliterator
# 転写システムの初期化
transliterator = Transliterator()
# 基本的な文章の転写
text = "向こうへ行く"
results = transliterator.analyze(text)
for token in results:
print(f"{token['orig']} -> {token['kana']} -> {token['hira']} -> {token['hepburn']}")
# 期待される出力例:
# 向こう -> ムコウ -> むこう -> mukou
# へ -> ヘ -> へ -> he
# 行く -> イク -> いく -> iku
マクロン使用の長音処理
# マクロンを使用した長音表記
text = "東京に行く"
results_macron = transliterator.analyze(text, use_macron=True)
results_normal = transliterator.analyze(text, use_macron=False)
print("=== マクロンあり ===")
for token in results_macron:
print(f"{token['orig']} -> {token['hepburn']}")
print("=== マクロンなし ===")
for token in results_normal:
print(f"{token['orig']} -> {token['hepburn']}")
# 期待される出力:
# === マクロンあり ===
# 東京 -> tōkyō
# に -> ni
# 行く -> iku
# === マクロンなし ===
# 東京 -> toukyou
# に -> ni
# 行く -> iku
複雑な文章の処理
# 漢字・ひらがな・カタカナ・英語混在文の処理
complex_text = "パーティーで美しい花を見る"
results = transliterator.analyze(complex_text, use_macron=True)
for token in results:
print(f"原文: '{token['orig']}'")
print(f" カナ: {token['kana']}")
print(f" ひら: {token['hira']}")
print(f" ローマ: {token['hepburn']}")
print()
# 期待される出力:
# 原文: 'パーティー'
# カナ: パーティー
# ひら: ぱーてぃー
# ローマ: pātī
#
# 原文: 'で'
# カナ: デ
# ひら: で
# ローマ: de
#
# 原文: '美しい'
# カナ: ウツクシイ
# ひら: うつくしい
# ローマ: utsukushii
文脈依存ルールの効果確認
# 文脈に依存する読み変更の例(「何」の読み分け)
test_cases = [
"何が好き?", # 何 -> ナニ (後続が「ガ」)
"何度も挑戦", # 何 -> ナン (後続が「ド」)
"何色ありますか?" # 何 -> ナニ (後続が「イ」)
]
for text in test_cases:
results = transliterator.analyze(text)
print(f"入力: {text}")
# 「何」トークンを探して読みを確認
for token in results:
if token['orig'] == '何':
print(f"「何」の読み: {token['kana']} -> {token['hepburn']}")
break
print()
# 期待される出力:
# 入力: 何が好き?
# 「何」の読み: ナニ -> nani
#
# 入力: 何度も挑戦
# 「何」の読み: ナン -> nan
#
# 入力: 何色ありますか?
# 「何」の読み: ナニ -> nani
特殊文字・記号の処理
# 記号・英数字混在テキストの処理
mixed_text = "ID:12345、URL:https://example.com"
results = transliterator.analyze(mixed_text)
for token in results:
print(f"'{token['orig']}' -> '{token['hepburn']}'")
# 期待される出力:
# 'ID' -> 'ID' # 英字はそのまま
# ':' -> ':' # 記号はそのまま
# '12345' -> '12345' # 数字はそのまま
# '、' -> '、' # 区切り記号はそのまま
# 'URL' -> 'URL' # 英字はそのまま
内部処理フロー
解析処理パイプライン
def analyze_pipeline_explained(self, text):
"""転写処理パイプラインの詳細説明"""
# 1. SudachiPy形態素解析
with self._tokenizer_lock:
tokens = self.tokenizer_obj.tokenize(text, self.mode)
results = []
# 2. 各トークンの処理
for token in tokens:
surface = token.surface() # 表層形
reading = token.reading_form() # 読み(カタカナ)
pos = token.part_of_speech() # 品詞情報
# 3. 記号・空白の特別処理
if pos and pos[0] in ["記号", "補助記号", "空白"]:
reading = surface # 記号は表層形をそのまま使用
# 4. 表層形と読みが同じ場合(ひらがな・記号等)
if surface == reading:
results.append({
"orig": surface,
"kana": reading,
"hira": surface, # そのまま
"hepburn": surface # そのまま
})
continue
# 5. 単一文字の処理
if len(surface) == 1:
results.append({
"orig": surface,
"kana": reading,
"hira": self.kata_to_hira(reading),
"hepburn": katakana_to_hepburn(reading, use_macron)
})
else:
# 6. 複数文字の漢字・送り仮名分離
parts = self.split_kanji_okurigana(surface, reading, use_macron)
results.extend(parts)
# 7. 文脈依存ルールの適用
try:
results = apply_context_rules(results, use_macron) or results
except Exception:
pass # ルール適用失敗時は元の結果を使用
# 8. ルール適用後の再計算
for entry in results:
kana = entry.get("kana", "")
if kana:
entry["hira"] = self.kata_to_hira(kana)
entry["hepburn"] = katakana_to_hepburn(kana, use_macron)
return results
漢字・送り仮名分離アルゴリズム
def split_algorithm_explained(surface, reading_kana):
"""分離アルゴリズムの詳細説明"""
# 1. 表層形のブロック分割
blocks = []
current_block = ""
prev_is_kanji = None
for char in surface:
is_kanji = Transliterator.is_kanji(char)
if prev_is_kanji is None or is_kanji == prev_is_kanji:
# 同じタイプの文字は同じブロックに
current_block += char
else:
# タイプが変わったら新しいブロック
blocks.append((prev_is_kanji, current_block))
current_block = char
prev_is_kanji = is_kanji
if current_block:
blocks.append((prev_is_kanji, current_block))
# 例: "向こう" -> [(True, "向"), (False, "こう")]
# "行く" -> [(True, "行"), (False, "く")]
# 2. 読みの分配
kana_len = len(reading_kana)
# 初期割当: 各ブロックの文字数に比例
allocations = [len(block_text) for _, block_text in blocks]
allocated_total = sum(allocations)
remaining = kana_len - allocated_total
# 3. 余った読みの分配(漢字ブロック優先)
if remaining > 0:
# まず漢字ブロックに分配
for i, (is_kanji, _) in enumerate(blocks):
if remaining <= 0:
break
if is_kanji:
allocations[i] += 1
remaining -= 1
# まだ余りがある場合は左から順に分配
i = 0
while remaining > 0 and len(blocks) > 0:
allocations[i] += 1
remaining -= 1
i = (i + 1) % len(blocks)
# 4. 読みが不足している場合は右から削減
if remaining < 0:
need_to_remove = -remaining
i = len(blocks) - 1
while need_to_remove > 0 and i >= 0:
can_remove = max(0, allocations[i] - 1)
remove_amount = min(can_remove, need_to_remove)
allocations[i] -= remove_amount
need_to_remove -= remove_amount
i -= 1
# 5. 最終的な読み分配
pos = 0
result = []
for (is_kanji, block_text), allocation in zip(blocks, allocations):
block_reading = reading_kana[pos:pos + allocation]
pos += allocation
result.append({
"orig": block_text,
"kana": block_reading,
"hira": Transliterator.kata_to_hira(block_reading),
"hepburn": katakana_to_hepburn(block_reading, use_macron)
})
return result
並行処理・スレッドセーフティ
ロック機構
class ThreadSafeUsage:
"""スレッドセーフな使用例"""
def __init__(self):
self.transliterator = Transliterator()
def process_texts_concurrently(self, texts):
"""複数テキストの並行処理"""
import concurrent.futures
def process_single(text):
return self.transliterator.analyze(text)
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
# 内部のロック機構により安全に並行実行
futures = [executor.submit(process_single, text) for text in texts]
results = [f.result() for f in futures]
return results
# 使用例
processor = ThreadSafeUsage()
texts = ["東京に行く", "大阪で食事", "名古屋を観光", "福岡に宿泊"]
results = processor.process_texts_concurrently(texts)
for i, result in enumerate(results):
print(f"テキスト{i+1}: {texts[i]}")
for token in result:
print(f" {token['orig']} -> {token['hepburn']}")
パフォーマンス考慮事項
# 大量テキスト処理のベストプラクティス
def efficient_batch_processing(texts, batch_size=100):
"""効率的なバッチ処理"""
transliterator = Transliterator()
results = []
for i in range(0, len(texts), batch_size):
batch = texts[i:i + batch_size]
batch_results = []
for text in batch:
# 各テキストを個別に処理(ロック制御あり)
result = transliterator.analyze(text)
batch_results.append(result)
results.extend(batch_results)
# バッチ間で少し休憩(メモリ管理)
if len(results) % 1000 == 0:
print(f"処理済み: {len(results)} テキスト")
return results
エラーハンドリング
例外処理
def safe_analyze(text):
"""安全な解析処理"""
transliterator = Transliterator()
try:
results = transliterator.analyze(text)
return results, None
except RuntimeError as e:
if "Already borrowed" in str(e):
# SudachiPyの並行アクセスエラー
print("並行アクセスエラーが発生しました。リトライします。")
return None, "RETRY_NEEDED"
else:
print(f"実行時エラー: {e}")
return None, "RUNTIME_ERROR"
except Exception as e:
print(f"予期しないエラー: {e}")
return None, "UNKNOWN_ERROR"
# 使用例(リトライ機構付き)
def analyze_with_retry(text, max_retries=3):
"""リトライ機構付き解析"""
for attempt in range(max_retries):
results, error = safe_analyze(text)
if results is not None:
return results
if error == "RETRY_NEEDED":
print(f"リトライ {attempt + 1}/{max_retries}")
import time
time.sleep(0.1 * (attempt + 1)) # 指数バックオフ
continue
else:
break
# 全てのリトライが失敗した場合のフォールバック
print("解析に失敗しました。フォールバック処理を実行します。")
return [{"orig": text, "kana": text, "hira": text, "hepburn": text}]
設定・カスタマイズ
SudachiPy設定
# カスタムSudachiPy設定での初期化
class CustomTransliterator(Transliterator):
def __init__(self, dict_type="full", split_mode="C"):
"""カスタム設定での初期化"""
# 辞書タイプの選択
dict_types = {
"small": dictionary.Dictionary.create(dict_type="small"),
"core": dictionary.Dictionary.create(dict_type="core"),
"full": dictionary.Dictionary.create(dict_type="full")
}
self.tokenizer_obj = dict_types.get(dict_type, dict_types["full"])
# 分割モードの選択
split_modes = {
"A": tokenizer.Tokenizer.SplitMode.A, # 短い単位
"B": tokenizer.Tokenizer.SplitMode.B, # 中間単位
"C": tokenizer.Tokenizer.SplitMode.C # 長い単位(デフォルト)
}
self.mode = split_modes.get(split_mode, split_modes["C"])
self._tokenizer_lock = threading.Lock()
# 使用例
# 短い単位での分割を使用
small_unit_transliterator = CustomTransliterator(dict_type="core", split_mode="A")
text = "取り敢えず検索してみる"
results = small_unit_transliterator.analyze(text)
for token in results:
print(f"{token['orig']} -> {token['hepburn']}")
テスト・デバッグ
包括的テストセット
def run_comprehensive_tests():
"""包括的な機能テスト"""
transliterator = Transliterator()
test_cases = [
# 基本的な文章
("向こうへ行く", "向こう", "ムコウ"),
("美しい花", "美しい", "ウツクシイ"),
# 文脈依存
("何度も", "何", "ナン"),
("何が", "何", "ナニ"),
# 外来語
("パーティー", "パーティー", "パーティー"),
("コンピューター", "コンピューター", "コンピューター"),
# 漢字・送り仮名
("取り敢えず", "取り", "トリ"),
("見知らぬ", "見知ら", "ミシラ"),
# 記号・英数字
("ID:12345", "ID", "ID"),
("SessionIDを取得", "SessionID", "SessionID")
]
for text, target_orig, expected_kana in test_cases:
results = transliterator.analyze(text)
# 対象トークンを検索
target_token = None
for token in results:
if token['orig'] == target_orig:
target_token = token
break
if target_token:
actual_kana = target_token['kana']
status = "✓" if actual_kana == expected_kana else "✗"
print(f"{status} {text}: {target_orig} -> {actual_kana} (期待値: {expected_kana})")
else:
print(f"✗ {text}: トークン '{target_orig}' が見つかりません")
run_comprehensive_tests()
依存関係
必須依存関係
sudachipy: 形態素解析エンジンthreading: 並行制御typing: 型ヒント
内部モジュール依存
transliteration_kana_to_hepburn: ヘボン式変換transliteration_context_rules: 文脈依存ルール
システム要件
- Python 3.7以上
- SudachiPy辞書ファイル(自動ダウンロード)
- 十分なメモリ(辞書読み込み用)
注意事項・制限
処理精度の制限
- 形態素解析結果に依存
- 未知語・固有名詞は読み推定
- 文脈によっては不正確な分割
パフォーマンス制限
- 初回実行時の辞書読み込み時間
- 大量テキスト処理時のメモリ使用量
- 並行アクセス時のロック待機
出力形式の制限
# 現在サポートしていない機能
unsupported_features = [
"アクセント記号(音調)",
"方言・古語の特殊読み",
"人名・地名の特殊読み",
"外国語の音写(中国語・韓国語等)",
"カスタム読み辞書",
"品詞情報の出力"
]
関連モジュール
transliteration_kana_to_hepburn.py: ヘボン式変換処理transliteration_context_rules.py: 文脈依存ルール適用config.py: システム設定管理utils.py: ユーティリティ関数
将来の改善点
- カスタム読み辞書対応
- より高精度な文脈解析
- 他言語音写システムとの統合
- リアルタイム処理最適化
- 分散処理対応