659 lines
19 KiB
Markdown
659 lines
19 KiB
Markdown
# transliteration_transliterator.py - 総合音写・転写システム
|
||
|
||
## 概要
|
||
|
||
SudachiPyを利用した日本語のローマ字転写システムのメインクラスです。形態素解析、漢字・送り仮名の分離、文脈依存ルールの適用、ヘボン式変換を統合し、高精度な日本語ローマ字化を提供します。
|
||
|
||
## 主要機能
|
||
|
||
### 統合転写システム
|
||
- SudachiPyによる高精度形態素解析
|
||
- 漢字・送り仮名の自動分離処理
|
||
- 文脈依存読み変更ルールの適用
|
||
|
||
### 多層変換処理
|
||
- カタカナ読み取得・分配
|
||
- ひらがな自動変換
|
||
- ヘボン式ローマ字生成
|
||
|
||
### 並行処理対応
|
||
- スレッドセーフなトークナイザー利用
|
||
- ロック機構による安全な並行実行
|
||
- 高負荷環境での安定動作
|
||
|
||
## クラス構造
|
||
|
||
### Transliterator クラス
|
||
```python
|
||
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
|
||
|
||
```python
|
||
def analyze(self, text: str, use_macron: bool = False) -> List[Dict[str, Any]]
|
||
```
|
||
|
||
テキストを解析して転写情報を生成
|
||
|
||
#### パラメータ
|
||
- **text**: 解析対象の日本語テキスト
|
||
- **use_macron**: マクロン使用フラグ(長音表記方式)
|
||
|
||
#### 戻り値
|
||
- **List[Dict[str, Any]]**: トークン転写情報のリスト
|
||
|
||
#### 出力辞書構造
|
||
```python
|
||
{
|
||
"orig": str, # 元の文字・文字列
|
||
"kana": str, # カタカナ読み
|
||
"hira": str, # ひらがな読み
|
||
"hepburn": str # ヘボン式ローマ字
|
||
}
|
||
```
|
||
|
||
### split_kanji_okurigana (静的メソッド)
|
||
|
||
```python
|
||
@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 (静的メソッド)
|
||
|
||
```python
|
||
@staticmethod
|
||
def is_kanji(ch: str) -> bool
|
||
```
|
||
|
||
文字が漢字かどうかを判定
|
||
|
||
#### パラメータ
|
||
- **ch**: 判定対象文字
|
||
|
||
#### 戻り値
|
||
- **bool**: 漢字判定結果
|
||
|
||
### kata_to_hira (静的メソッド)
|
||
|
||
```python
|
||
@staticmethod
|
||
def kata_to_hira(text: str) -> str
|
||
```
|
||
|
||
カタカナをひらがなに変換
|
||
|
||
#### パラメータ
|
||
- **text**: 変換対象のカタカナテキスト
|
||
|
||
#### 戻り値
|
||
- **str**: ひらがな変換結果
|
||
|
||
## 使用方法
|
||
|
||
### 基本的な転写処理
|
||
|
||
```python
|
||
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
|
||
```
|
||
|
||
### マクロン使用の長音処理
|
||
|
||
```python
|
||
# マクロンを使用した長音表記
|
||
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
|
||
```
|
||
|
||
### 複雑な文章の処理
|
||
|
||
```python
|
||
# 漢字・ひらがな・カタカナ・英語混在文の処理
|
||
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
|
||
```
|
||
|
||
### 文脈依存ルールの効果確認
|
||
|
||
```python
|
||
# 文脈に依存する読み変更の例(「何」の読み分け)
|
||
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
|
||
```
|
||
|
||
### 特殊文字・記号の処理
|
||
|
||
```python
|
||
# 記号・英数字混在テキストの処理
|
||
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' # 英字はそのまま
|
||
```
|
||
|
||
## 内部処理フロー
|
||
|
||
### 解析処理パイプライン
|
||
|
||
```python
|
||
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
|
||
```
|
||
|
||
### 漢字・送り仮名分離アルゴリズム
|
||
|
||
```python
|
||
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
|
||
```
|
||
|
||
## 並行処理・スレッドセーフティ
|
||
|
||
### ロック機構
|
||
|
||
```python
|
||
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']}")
|
||
```
|
||
|
||
### パフォーマンス考慮事項
|
||
|
||
```python
|
||
# 大量テキスト処理のベストプラクティス
|
||
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
|
||
```
|
||
|
||
## エラーハンドリング
|
||
|
||
### 例外処理
|
||
|
||
```python
|
||
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設定
|
||
|
||
```python
|
||
# カスタム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']}")
|
||
```
|
||
|
||
## テスト・デバッグ
|
||
|
||
### 包括的テストセット
|
||
|
||
```python
|
||
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辞書ファイル(自動ダウンロード)
|
||
- 十分なメモリ(辞書読み込み用)
|
||
|
||
## 注意事項・制限
|
||
|
||
### 処理精度の制限
|
||
- 形態素解析結果に依存
|
||
- 未知語・固有名詞は読み推定
|
||
- 文脈によっては不正確な分割
|
||
|
||
### パフォーマンス制限
|
||
- 初回実行時の辞書読み込み時間
|
||
- 大量テキスト処理時のメモリ使用量
|
||
- 並行アクセス時のロック待機
|
||
|
||
### 出力形式の制限
|
||
```python
|
||
# 現在サポートしていない機能
|
||
unsupported_features = [
|
||
"アクセント記号(音調)",
|
||
"方言・古語の特殊読み",
|
||
"人名・地名の特殊読み",
|
||
"外国語の音写(中国語・韓国語等)",
|
||
"カスタム読み辞書",
|
||
"品詞情報の出力"
|
||
]
|
||
```
|
||
|
||
## 関連モジュール
|
||
|
||
- `transliteration_kana_to_hepburn.py`: ヘボン式変換処理
|
||
- `transliteration_context_rules.py`: 文脈依存ルール適用
|
||
- `config.py`: システム設定管理
|
||
- `utils.py`: ユーティリティ関数
|
||
|
||
## 将来の改善点
|
||
|
||
- カスタム読み辞書対応
|
||
- より高精度な文脈解析
|
||
- 他言語音写システムとの統合
|
||
- リアルタイム処理最適化
|
||
- 分散処理対応 |