📖

MeCab+UniDicでルビ表示を正しく実装する方法

に公開

MeCab+UniDicでルビ表示を正しく実装する方法

日本語の形態素解析システムでルビ(振り仮名)を正確に表示することは、単純に見えて実は非常に複雑な問題です。本記事では、MeCab + UniDicを使った形態素解析において、「映画」が「ええが」、「生活」が「せえかつ」と誤って表示される問題を解決する実装方法を詳しく解説します。

背景:なぜルビ表示は難しいのか

日本語の形態素解析でルビ表示を実装する際、最も大きな課題は長音記号「ー」の適切な変換です。MeCabの解析結果では、多くの単語の読みが長音記号を含んだ形で返されます:

  • 「映画」→「エーガ」
  • 「生活」→「セーカツ」
  • 「研究」→「ケンキュー」

しかし、実際のルビとして表示すべき正しい仮名は以下のようになります:

  • 「映画」→「えいが」
  • 「生活」→「せいかつ」
  • 「研究」→「けんきゅう」

問題の原因

複雑な長音変換ロジックの実装

// 間違いやすい実装例
if ('エケゲセゼテデネヘベペメレ'.includes(prevChar)) {
  return 'イ';  // エ段の長音は「イ」に変換
}

このような単純な規則では、日本語の複雑な音韻規則に対応できません。特に:

  • 拗音(ゃゅょ)の処理
  • 連濁の処理
  • 活用形での読みの変化

これらを全て自前で実装すると、コードが複雑になり保守が困難になります。

解決方法:UniDicのformBaseフィールドを活用

UniDicには実は正しい仮名表記を提供するフィールドが既に存在しています。

UniDicの重要フィールド比較

フィールド名 映画 生活 説明
surface_reading エーガ セーカツ 長音記号あり(発音用)
formBase エイガ セイカツ 正しい仮名表記
goshu エーガ セーカツ 語種情報

実装例

改善前:複雑な長音変換ロジック

// 長音記号を変換する複雑なロジック
finalReading = token.surface_reading.replace(//g, (match, offset) => {
  if (offset > 0) {
    const prevChar = token.surface_reading[offset - 1];
    // エ段の長音は「イ」に変換
    if ('エケゲセゼテデネヘベペメレ'.includes(prevChar)) {
      return 'イ';
    }
    // オ段の長音は「ウ」に変換
    if ('オコゴソゾトドノホボポモヨロヲ'.includes(prevChar)) {
      return 'ウ';
    }
    // その他多くの条件分岐...
  }
  return match;
});

改善後:UniDicのformBaseを利用

// UniDicのformBaseフィールドを優先的に使用(これが正しい仮名表記)
let finalReading = '';
if (token.formBase && token.formBase !== '*' && !containsKanji(token.formBase)) {
  // formBaseがカタカナで正しい読みを持っている
  finalReading = token.formBase;
} else if (token.conjugated_reading && token.conjugated_reading !== '*') {
  // AWS Lambdaが生成した活用形の読み
  finalReading = token.conjugated_reading;
} else if (reading && !containsKanji(reading)) {
  // 基本的な読み
  finalReading = reading;
} else {
  // フォールバック
  finalReading = '';
}

デバッグ方法

UniDicのフィールドを確認する方法:

# APIエンドポイントで確認
curl -X POST http://localhost:3000/api/analyze \
  -H "Content-Type: application/json" \
  -d '{"text": "映画"}' | jq '.raw_aws_result.tokens[0]'

重要なフィールドの確認:

# Pythonでの確認例
import sys, json
data = json.load(sys.stdin)
token = data.get('raw_aws_result', {}).get('tokens', [])[0]
print(f"surface_reading: {token.get('surface_reading')}")  # エーガ
print(f"formBase: {token.get('formBase')}")                # エイガ(正解!)
print(f"goshu: {token.get('goshu')}")                      # エーガ

Lambda API側の実装

MeCab + UniDicを使用したPython実装:

# UniDicのフィールド定義
unidic_fields = [
    'pos',              # 品詞
    'pos_detail_1',     # 品詞細分類1
    'pos_detail_2',     # 品詞細分類2
    'pos_detail_3',     # 品詞細分類3
    'conjugation_type', # 活用型
    'conjugated_form',  # 活用形
    'base_form',        # 原形
    'reading',          # 原形の読み
    'surface_form',     # 表層形
    'surface_reading',  # 表層形の読み(活用形の読み)
    'lemma',            # 語彙素
    'goshu',            # 語種
    'orthBase',         # 書字形基本形
    'orthVariant',      # 表記
    'formBase',         # 形態素基本形(重要!)
    'formVariant',      # 異形態
]

# フィールドの抽出
for i, field in enumerate(unidic_fields):
    if i < len(features):
        token[field] = features[i]

実装のポイント

1. 優先順位の設定

// 読み仮名の候補を優先順位で取得
const readingCandidates = [
  token.formBase,           // 最優先:正しい仮名表記
  token.conjugated_reading,  // 活用形の読み
  token.reading,            // 基本的な読み
];

2. 漢字判定の実装

function containsKanji(text: string): boolean {
  if (!text) return false;
  return /[\u4e00-\u9faf]/.test(text);
}

3. カタカナ→ひらがな変換

function katakanaToHiragana(text: string): string {
  if (!text) return '';
  return text.replace(/[\u30A1-\u30FF]/g, (match) => {
    const code = match.charCodeAt(0);
    if (code >= 0x30A1 && code <= 0x30F6) {
      return String.fromCharCode(code - 0x60);
    }
    return match;
  });
}

パフォーマンス最適化

// キャッシュを活用
const CACHE_SIZE = 500;
const analysisCache = new Map<string, any>();

// キャッシュチェック
if (analysisCache.has(cacheKey)) {
  return analysisCache.get(cacheKey);
}

// 結果をキャッシュ
analysisCache.set(cacheKey, result);

まとめ

日本語のルビ表示で正確な結果を得るためには:

  1. UniDicのformBaseフィールドを優先的に使用する
  2. 複雑な音韻変換ロジックを自前で実装しない
  3. 信頼できる辞書データソースを最大限活用する

この方法により:

  • コードがシンプルになり保守性が向上
  • 「映画」→「えいが」、「生活」→「せいかつ」など正確な表示が実現
  • 日本語の複雑な特性への個別対応が不要に

UniDicには既に必要なデータが全て揃っているため、それを正しく活用することが重要です。

Discussion