日本語アナライザーの比較:Kuromoji・Sudachi・MeCab・Janome・LLMの性能検証

BLOG

Elasticsearchの標準アナライザーは Kuromoji ですが、他にも日本語向けのアナライザーが存在します。本記事では Sudachi や MeCab、およびPythonライブラリの Janome、そして LLM(GPT-4) といった選択肢を比較し、どんな場面でどれを使うべきかを検討しました。

なお、Elasticsearch 9.xではSudachiやMeCabの公式対応プラグインはまだリリースされていません。そのため今回は Python環境で事前に形態素解析してからElasticsearchにインデックスする方法を採用しています。一方、Janomeは純Python実装であるため、追加プラグインなしで使用可能です。

比較対象アナライザー

1. Elasticsearchプラグイン系

  Kuromoji:Elasticsearch標準の日本語アナライザー

2. Pythonライブラリ系(アプリ側で事前解析)

  • SudachiPy:3種類の粒度(A/B/C)に対応
  • MeCab:高速かつ実績豊富な形態素解析器
  • Janome:Pure Pythonで導入が簡単

3. LLM系(外部API)

  OpenAI GPT-4o:文脈を考慮した柔軟な分割が可能

ステップ0:事前準備

1. 仮想環境の作成(uvを使用)

curl -LsSf https://astral.sh/uv/install.sh | sh
mkdir analyzer-project && cd analyzer-project
uv venv
source .venv/bin/activate

2. 必要なPythonライブラリのインストール

uv pip install elasticsearch janome openai sudachipy sudachidict_core mecab-python3

3. Elasticsearchプラグインの確認(Kuromoji)

bin/elasticsearch-plugin install analysis-kuromoji

4. MeCab本体のインストール(macOS向け)

brew install mecab mecab-ipadic

5. OpenAI APIキーの設定

export OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Python環境とテキスト設定

import os
import sys
import platform
import re
from dotenv import load_dotenv

# Import the working elasticsearch connection utility
from elastic_conection import es, test_connection

# Japanese text analysis libraries
import MeCab
from janome.tokenizer import Tokenizer
from sudachipy import tokenizer as sudachi_tokenizer
from sudachipy import dictionary as sudachi_dictionary

# OpenAI for LLM comparison
from openai import OpenAI

# Environment and target text setup
print("Python環境情報")
print("="*30)
print(f"Python Version: {sys.version.split()[0]}")
print(f"Platform: {platform.platform()}")
print(f"Architecture: {platform.machine()}")

# Check if in virtual environment
if hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix):
    print("仮想環境で実行中")
else:
    print("仮想環境ではありません")

print(f"実行環境: {sys.executable}")

# Define the target text for analysis
TARGET_TEXT = """ロキソニン錠の説明書ですね。ロキソニンは、解熱鎮痛作用のある非ステロイド性抗炎症薬(NSAIDs)で、
痛みや発熱、炎症を抑える効果があります。具体的には、関節リウマチ、変形性関節症、腰痛症、肩こり、
歯痛、手術後や外傷後の炎症や痛み、風邪による熱や痛みなどに用いられます。"""

print(f"\n分析対象テキスト: {TARGET_TEXT}")
print(f"文字数: {len(TARGET_TEXT)}")
print()

Python環境情報
==============================
Python Version: 3.13.3
Platform: macOS-15.5-arm64-arm-64bit-Mach-O
Architecture: arm64 仮想環境で実行中
実行環境: /Users/*****/es-analyzer-project/.venv/bin/python
分析対象テキスト: ロキソニン錠の説明書ですね。ロキソニンは、解熱鎮痛作用のある非ステロイド性抗炎症薬(NSAIDs)で、 痛みや発熱、炎症を抑える効果があります。具体的には、関節リウマチ、変形性関節症、腰痛症、肩こり、 歯痛、手術後や外傷後の炎症や痛み、風邪による熱や痛みなどに用いられます。
文字数: 137

トークン正規化・クリーニング関数

Kuromoji を基準(ベースライン)として使用 し、他のアナライザーの結果をKuromojiレベルまでクリーニングして比較します。

比較戦略

1. Kuromoji = ベースライン

  • クリーニングなし: Kuromojiの生出力をそのまま使用
  • 理由: Elasticsearchに最適化済み、自然にストップワード除去済み
  • 役割: 他のアナライザーの目標レベルとして機能

2. 他のアナライザー = Kuromojiレベルまでクリーニング

  • MeCab, SudachiPy, Janome, OpenAI: クリーニング関数を適用
  • 目標: Kuromojiと同等の品質レベルに調整
  • 比較: クリーニング後にKuromojiとの類似度を測定

クリーニング処理内容(Kuromoji以外)

  • 1. 助詞・助動詞の除去: 「は」「を」「に」「が」など機能語の削除
  • 2. 句読点・記号の除去: 「、」「。」「(」「)」などの除去
  • 3. ストップワードの除去: 検索で意味の薄い語の削除
  • 4. 空白・数字の除去: 純粋な数字や空白文字の除去

期待される効果

  • 公平な比較: 全アナライザーが同じ品質レベルで比較される
  • 実用性評価: 検索エンジンでの実際の使用場面を想定
  • 最適化効果: 各アナライザーのクリーニング後の性能向上を確認
  • Kuromoji優位性: Elasticsearchプラグインとしての最適化効果を確認
def clean_tokens_like_kuromoji(tokens):
    """
    Clean tokens to match Kuromoji's behavior by removing stop words and punctuation.
    
    Args:
        tokens (list): List of token strings
        
    Returns:
        list: Cleaned tokens with stop words and punctuation removed
    """
    
    # Japanese stop words and particles commonly filtered out
    stop_words = {
        'は', 'の', 'を', 'に', 'が', 'で', 'と', 'から', 'まで', 'より', 'へ',
        'や', 'か', 'も', 'て', 'た', 'だ', 'である', 'です', 'ます', 'した',
        'する', 'ある', 'いる', 'なる', 'この', 'その', 'あの', 'どの',
        'これ', 'それ', 'あれ', 'どれ', 'ここ', 'そこ', 'あそこ', 'どこ',
        'こう', 'そう', 'ああ', 'どう', 'という', 'といった', 'による',
        'において', 'について', 'に関して', 'に対して', 'に関する', 'について',
        'さん', 'ちゃん', 'くん', 'さま', 'さあ', 'まあ', 'ああ', 'いや',
        'はい', 'いいえ', 'うん', 'ううん', 'えー', 'あー', 'うー', 'おー'
    }
    
    # Punctuation and symbols to remove
    punctuation_patterns = [
        r'^[、。!?.,!?;:()()\[\]「」『』【】〈〉《》〔〕…‥・]+$',  # Pure punctuation
        r'^[ー\-~〜]+$',  # Long vowel marks and dashes
        r'^[ \s]+$',     # Whitespace (including full-width)
        r'^\d+$',         # Pure numbers
        r'^[a-zA-Z]+$',   # Pure alphabet
        r'^[0-9]+$',    # Full-width numbers
        r'^[a-zA-Z]+$' # Full-width alphabet
    ]
    
    cleaned = []
    for token in tokens:
        if not token or not token.strip():
            continue
            
        # Remove stop words
        if token in stop_words:
            continue
            
        # Remove punctuation and unwanted patterns
        is_punctuation = False
        for pattern in punctuation_patterns:
            if re.match(pattern, token):
                is_punctuation = True
                break
                
        if not is_punctuation:
            cleaned.append(token)
    
    return cleaned

print("Token cleaning function defined")

トークナイザー初期化・接続確認

  • 各トークナイザーを初期化し、動作確認を行います。

初期化対象:

1. Elasticsearch + Kuromoji

  • ローカルElasticsearchサーバー(localhost:9200)への接続
  • Kuromojiアナライザーの利用可能性確認

2. MeCab

  • Homebrew環境のIPA辞書パス設定
  • 分かち書きモード(-O wakati)での初期化

3. SudachiPy

  • 標準辞書の読み込み
  • A/B/Cモード対応トークナイザーオブジェクト作成

4. Janome

  • 純Python実装のトークナイザー初期化
  • 依存関係なしの簡単セットアップ

5. OpenAI GPT-4o

  • 環境変数からAPIキー取得
  • GPT-4モデルへの接続確認

注意: 各ツールが正常に初期化されない場合、エラーメッセージが表示されます。

# === システム接続確認とトークナイザー初期化 ===
print("=== システム接続確認 ===")

# Elasticsearch接続と初期化
print("Elasticsearchに接続中...")
try:
    # elastic_conection.pyからESクライアントをインポート
    from elastic_conection import es, test_connection
    
    # 接続テスト
    if test_connection():
        es_available = True
        print("Elasticsearch接続成功")
    else:
        es_available = False
        print("Elasticsearch接続失敗")
        print("解決方法: Elasticsearchサーバーを起動してください")
        print("   brew services start elasticsearch")
        
except Exception as e:
    es_available = False
    print(f"Elasticsearch接続失敗: {e}")
    print("解決方法: Elasticsearchサーバーを起動してください")
    print("   brew services start elasticsearch")

# 各トークナイザーの初期化
print("\nトークナイザーを初期化中...")

# MeCab初期化 (Homebrew環境対応)
tagger = None
try:
    import MeCab
    
    # Homebrew環境用の設定リスト(正しいmecabrcパスを含む)
    configs_to_try = [
        "-r /opt/homebrew/etc/mecabrc -Owakati",  # Homebrew mecabrc + wakati mode
        "-r /opt/homebrew/etc/mecabrc",            # Homebrew mecabrc only  
        "-Owakati",                                # wakati mode only
        "",                                        # デフォルト設定
        "-d /opt/homebrew/lib/mecab/dic/ipadic",   # ipadic辞書パス (Homebrew)
        "-d /usr/local/lib/mecab/dic/ipadic",      # ipadic辞書パス (従来のパス)
    ]
    
    for config in configs_to_try:
        try:
            print(f"   MeCab設定を試行中: '{config if config else 'デフォルト'}'")
            tagger = MeCab.Tagger(config)
            # テスト実行して動作確認
            test_result = tagger.parse("テスト")
            if test_result and len(test_result.strip()) > 0:
                mecab_config = config if config else "デフォルト設定"
                print(f"MeCab初期化成功 (設定: {mecab_config})")
                print(f"   テスト結果: {test_result.strip()}")
                break
        except Exception as e:
            print(f"   設定失敗: {e}")
            continue
    
    if not tagger:
        print("MeCab初期化失敗: MeCab could not be initialized with any configuration")
        print("解決方法: brew install mecab mecab-ipadic")
        
except ImportError:
    print("MeCab初期化失敗: MeCabがインストールされていません")
    print("解決方法: brew install mecab mecab-ipadic")

# SudachiPy初期化
tokenizer_obj = None
try:
    from sudachipy import tokenizer
    from sudachipy import dictionary
    
    tokenizer_obj = dictionary.Dictionary().create()
    print("SudachiPy初期化成功")
except Exception as e:
    print(f"SudachiPy初期化失敗: {e}")

# Janome初期化
janome_tokenizer = None
try:
    from janome.tokenizer import Tokenizer
    janome_tokenizer = Tokenizer()
    print("Janome初期化成功")
except Exception as e:
    print(f"Janome初期化失敗: {e}")

# OpenAI API初期化
openai_client = None
try:
    import openai
    from dotenv import load_dotenv
    import os
    
    load_dotenv()
    api_key = os.getenv("OPENAI_API_KEY")
    
    if api_key:
        openai_client = openai.OpenAI(api_key=api_key)
        print("OpenAI API初期化成功")
    else:
        print("OpenAI API初期化失敗: APIキーが設定されていません")
        print("解決方法: .envファイルにOPENAI_API_KEYを設定してください")
        
except Exception as e:
    print(f"OpenAI API初期化失敗: {e}")

# 初期化サマリー
print("\n初期化サマリー:")
print(f"   Elasticsearch: {'OK' if es_available else 'NG'}")
print(f"   MeCab: {'OK' if tagger else 'NG'}")
print(f"   SudachiPy: {'OK' if tokenizer_obj else 'NG'}")
print(f"   Janome: {'OK' if janome_tokenizer else 'NG'}")
print(f"   OpenAI: {'OK' if openai_client else 'NG'}")

トークン化関数の定義(共通フォーマット出力)

  • tokenize_with_kuromoji(text)
  • tokenize_with_mecab(text)
  • tokenize_with_sudachi(text, mode)
  • tokenize_with_janome(text)
  • tokenize_with_openai(text)

すべての関数でエラーハンドリングを実装し、失敗時は空リストを返します。

def tokenize_with_kuromoji(text):
    """Tokenize text using Elasticsearch Kuromoji analyzer"""
    if not es_available:
        print("Kuromoji tokenization skipped: Elasticsearch not available")
        return []
        
    try:
        response = es.indices.analyze(
            body={
                "analyzer": "kuromoji",
                "text": text
            }
        )
        return [token['token'] for token in response['tokens']]
    except Exception as e:
        print(f"Kuromoji tokenization error: {e}")
        return []

def tokenize_with_mecab(text):
    """Tokenize text using MeCab"""
    if not tagger:
        print("MeCab tokenization skipped: MeCab not available")
        return []
        
    try:
        result = tagger.parse(text).strip().split()
        return [token for token in result if token]
    except Exception as e:
        print(f"MeCab tokenization error: {e}")
        return []

def tokenize_with_sudachi(text, mode='C'):
    """Tokenize text using SudachiPy with specified mode (A, B, or C)"""
    if not tokenizer_obj:
        print("SudachiPy tokenization skipped: SudachiPy not available")
        return []
        
    try:
        mode_map = {'A': sudachi_tokenizer.Tokenizer.SplitMode.A,
                   'B': sudachi_tokenizer.Tokenizer.SplitMode.B,
                   'C': sudachi_tokenizer.Tokenizer.SplitMode.C}
        
        tokens = tokenizer_obj.tokenize(text, mode_map[mode])
        return [token.surface() for token in tokens]
    except Exception as e:
        print(f"SudachiPy tokenization error: {e}")
        return []

def tokenize_with_janome(text):
    """Tokenize text using Janome"""
    if not janome_tokenizer:
        print("Janome tokenization skipped: Janome not available")
        return []
        
    try:
        tokens = janome_tokenizer.tokenize(text, wakati=True)
        return list(tokens)
    except Exception as e:
        print(f"Janome tokenization error: {e}")
        return []

def tokenize_with_openai(text):
    """Tokenize text using OpenAI GPT-4"""
    if not openai_client:
        print("OpenAI tokenization skipped: OpenAI client not available")
        return []
    
    try:
        prompt = f"""
Please tokenize the following Japanese text into meaningful segments. 
Return only a comma-separated list of tokens, no explanations.

Text: {text}

Tokens:"""

        response = openai_client.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}],
            max_tokens=200,
            temperature=0
        )
        
        result = response.choices[0].message.content.strip()
        tokens = [token.strip() for token in result.split(',')]
        return [token for token in tokens if token]
    
    except Exception as e:
        print(f"OpenAI tokenization error: {e}")
        return []

print("All tokenization functions defined (with availability checks)")

包括的トークナイザー比較分析

def compare_all_tokenizers(text):
    """Compare all tokenizers on the given text - using Kuromoji as baseline (no cleaning)"""
    
    print(f"分析対象テキスト: {text}")
    print("=" * 80)
    
    results = {}
    
    # 1. Kuromoji (Elasticsearch) - BASELINE (no cleaning applied)
    print("\n1. Kuromoji (Elasticsearch) - ベースライン")
    kuromoji_tokens = tokenize_with_kuromoji(text)
    results['kuromoji'] = {
        'raw': kuromoji_tokens,
        'cleaned': kuromoji_tokens  # No cleaning - use as baseline
    }
    print(f"Raw/Baseline ({len(kuromoji_tokens)}): {kuromoji_tokens}")
    print("Kuromojiはベースラインとして使用(クリーニングなし)")
    
    # 2. SudachiPy (all modes) - cleaned to match Kuromoji behavior
    for mode in ['A', 'B', 'C']:
        print(f"\n2. SudachiPy Mode {mode} - Kuromojiベース調整済み")
        sudachi_tokens = tokenize_with_sudachi(text, mode)
        sudachi_cleaned = clean_tokens_like_kuromoji(sudachi_tokens)
        results[f'sudachi_{mode}'] = {
            'raw': sudachi_tokens,
            'cleaned': sudachi_cleaned
        }
        print(f"Raw ({len(sudachi_tokens)}): {sudachi_tokens}")
        print(f"Cleaned ({len(sudachi_cleaned)}): {sudachi_cleaned}")
    
    # 3. MeCab - cleaned to match Kuromoji behavior
    print(f"\n3. MeCab - Kuromojiベース調整済み")
    mecab_tokens = tokenize_with_mecab(text)
    mecab_cleaned = clean_tokens_like_kuromoji(mecab_tokens)
    results['mecab'] = {
        'raw': mecab_tokens,
        'cleaned': mecab_cleaned
    }
    print(f"Raw ({len(mecab_tokens)}): {mecab_tokens}")
    print(f"Cleaned ({len(mecab_cleaned)}): {mecab_cleaned}")
    
    # 4. Janome - cleaned to match Kuromoji behavior
    print(f"\n4. Janome - Kuromojiベース調整済み")
    janome_tokens = tokenize_with_janome(text)
    janome_cleaned = clean_tokens_like_kuromoji(janome_tokens)
    results['janome'] = {
        'raw': janome_tokens,
        'cleaned': janome_cleaned
    }
    print(f"Raw ({len(janome_tokens)}): {janome_tokens}")
    print(f"Cleaned ({len(janome_cleaned)}): {janome_cleaned}")
    
    # 5. OpenAI GPT-4 - cleaned to match Kuromoji behavior
    if openai_client:
        print(f"\n5. OpenAI GPT-4 - Kuromojiベース調整済み")
        openai_tokens = tokenize_with_openai(text)
        openai_cleaned = clean_tokens_like_kuromoji(openai_tokens)
        results['openai'] = {
            'raw': openai_tokens,
            'cleaned': openai_cleaned
        }
        print(f"Raw ({len(openai_tokens)}): {openai_tokens}")
        print(f"Cleaned ({len(openai_cleaned)}): {openai_cleaned}")
    else:
        print(f"\n5. OpenAI GPT-4 (スキップ - API key not available)")
    
    # Comparison summary with Kuromoji as baseline
    print(f"\nKuromojiベースライン比較:")
    kuromoji_baseline = set(results['kuromoji']['cleaned'])
    for name, data in results.items():
        if name != 'kuromoji':
            cleaned_tokens = set(data['cleaned'])
            overlap = len(kuromoji_baseline & cleaned_tokens)
            total_unique = len(kuromoji_baseline | cleaned_tokens)
            similarity = (overlap / total_unique * 100) if total_unique > 0 else 0
            print(f"  {name:<12}: {similarity:.1f}% similarity to Kuromoji baseline")
    
    return results

# Run the comparison
analysis_results = compare_all_tokenizers(TARGET_TEXT)

📊 Kuromoji(ベースライン)出力

  • トークン数:42
  • 特徴:分かち書きが細かく、Elasticsearchでのインデックスに最適

📊 Sudachi A/B/C、MeCab、Janome、GPT-4 出力

  • Sudachiモードごとに粒度が変化
  • GPT-4oは語彙的まとまり重視で分割が異なる
  • MeCab・Janomeは細かく分かれ、類似度が高い

1. Kuromoji (Elasticsearch) - ベースライン
Raw/Baseline (42): ['ロキソニン', '錠', '説明', '書', 'ロキソニン', '解熱', '鎮痛', '作用', '非', 'ステロイド', '性', '抗', '炎症', '薬', 'nsaids', '痛み', '発熱', '炎症', '抑える', '効果', '具体', '的', '関節', 'リウマチ', '変形', '性', '関節', '症', '腰痛', '症', '肩こり', '歯痛', '手術', '後', '外傷', '後', '炎症', '痛む', '風邪', '熱', '痛み', '用いる']
Kuromojiはベースラインとして使用(クリーニングなし)

2. SudachiPy Mode A 
Raw (86): ['ロキソニン', '錠', 'の', '説明', '書', 'です', 'ね', '。', 'ロキソニン', 'は', '、', '解熱', '鎮痛', '作用', 'の', 'ある', '非', 'ステロイド', '性', '抗', '炎症', '薬', '(', 'NSAIDs', ')', 'で', '、', '\n', '痛', 'み', 'や', '発熱', '、', '炎症', 'を', '抑える', '効果', 'が', 'あり', 'ます', '。', '具体', '的', 'に', 'は', '、', '関節', 'リウマチ', '、', '変形', '性', '関節', '症', '、', '腰痛', '症', '、', '肩こり', '、', '\n', '歯痛', '、', '手術', '後', 'や', '外傷', '後', 'の', '炎症', 'や', '痛', 'み', '、', '風邪', 'に', 'よる', '熱', 'や', '痛', 'み', 'など', 'に', '用い', 'られ', 'ます', '。']

Cleaned (49): ['ロキソニン', '錠', '説明', '書', 'ね', 'ロキソニン', '解熱', '鎮痛', '作用', '非', 'ステロイド', '性', '抗', '炎症', '薬', '痛', 'み', '発熱', '炎症', '抑える', '効果', 'あり', '具体', '的', '関節', 'リウマチ', '変形', '性', '関節', '症', '腰痛', '症', '肩こり', '歯痛', '手術', '後', '外傷', '後', '炎症', '痛', 'み', '風邪', 'よる', '熱', '痛', 'み', 'など', '用い', 'られ']

2. SudachiPy Mode B
Raw (78): ['ロキソニン', '錠', 'の', '説明書', 'です', 'ね', '。', 'ロキソニン', 'は', '、', '解熱', '鎮痛', '作用', 'の', 'ある', '非', 'ステロイド', '性', '抗', '炎症', '薬', '(', 'NSAIDs', ')', 'で', '、', '\n', '痛み', 'や', '発熱', '、', '炎症', 'を', '抑える', '効果', 'が', 'あり', 'ます', '。', '具体的', 'に', 'は', '、', '関節', 'リウマチ', '、', '変形性', '関節症', '、', '腰痛症', '、', '肩こり', '、', '\n', '歯痛', '、', '手術', '後', 'や', '外傷', '後', 'の', '炎症', 'や', '痛み', '、', '風邪', 'に', 'よる', '熱', 'や', '痛み', 'など', 'に', '用い', 'られ', 'ます', '。']

Cleaned (41): ['ロキソニン', '錠', '説明書', 'ね', 'ロキソニン', '解熱', '鎮痛', '作用', '非', 'ステロイド', '性', '抗', '炎症', '薬', '痛み', '発熱', '炎症', '抑える', '効果', 'あり', '具体的', '関節', 'リウマチ', '変形性', '関節症', '腰痛症', '肩こり', '歯痛', '手術', '後', '外傷', '後', '炎症', '痛み', '風邪', 'よる', '熱', '痛み', 'など', '用い', 'られ']

2. SudachiPy Mode C
Raw (78): ['ロキソニン', '錠', 'の', '説明書', 'です', 'ね', '。', 'ロキソニン', 'は', '、', '解熱', '鎮痛', '作用', 'の', 'ある', '非', 'ステロイド', '性', '抗', '炎症', '薬', '(', 'NSAIDs', ')', 'で', '、', '\n', '痛み', 'や', '発熱', '、', '炎症', 'を', '抑える', '効果', 'が', 'あり', 'ます', '。', '具体的', 'に', 'は', '、', '関節', 'リウマチ', '、', '変形性', '関節症', '、', '腰痛症', '、', '肩こり', '、', '\n', '歯痛', '、', '手術', '後', 'や', '外傷', '後', 'の', '炎症', 'や', '痛み', '、', '風邪', 'に', 'よる', '熱', 'や', '痛み', 'など', 'に', '用い', 'られ', 'ます', '。']

Cleaned (41): ['ロキソニン', '錠', '説明書', 'ね', 'ロキソニン', '解熱', '鎮痛', '作用', '非', 'ステロイド', '性', '抗', '炎症', '薬', '痛み', '発熱', '炎症', '抑える', '効果', 'あり', '具体的', '関節', 'リウマチ', '変形性', '関節症', '腰痛症', '肩こり', '歯痛', '手術', '後', '外傷', '後', '炎症', '痛み', '風邪', 'よる', '熱', '痛み', 'など', '用い', 'られ']

3. MeCab
Raw (80): ['ロキソニン', '錠', 'の', '説明', '書', 'です', 'ね', '。', 'ロキソニン', 'は', '、', '解熱', '鎮痛', '作用', 'の', 'ある', '非', 'ステロイド', '性', '抗', '炎症', '薬', '(', 'NSAIDs', ')', 'で', '、', '痛み', 'や', '発熱', '、', '炎症', 'を', '抑える', '効果', 'が', 'あり', 'ます', '。', '具体', '的', 'に', 'は', '、', '関節', 'リウマチ', '、', '変形', '性', '関節', '症', '、', '腰痛', '症', '、', '肩こり', '、', '歯痛', '、', '手術', '後', 'や', '外傷', '後', 'の', '炎症', 'や', '痛み', '、', '風邪', 'による', '熱', 'や', '痛み', 'など', 'に', '用い', 'られ', 'ます', '。']

Cleaned (45): ['ロキソニン', '錠', '説明', '書', 'ね', 'ロキソニン', '解熱', '鎮痛', '作用', '非', 'ステロイド', '性', '抗', '炎症', '薬', '痛み', '発熱', '炎症', '抑える', '効果', 'あり', '具体', '的', '関節', 'リウマチ', '変形', '性', '関節', '症', '腰痛', '症', '肩こり', '歯痛', '手術', '後', '外傷', '後', '炎症', '痛み', '風邪', '熱', '痛み', 'など', '用い', 'られ']

4. Janome
Raw (82): ['ロキソニン', '錠', 'の', '説明', '書', 'です', 'ね', '。', 'ロキソニン', 'は', '、', '解熱', '鎮痛', '作用', 'の', 'ある', '非', 'ステロイド', '性', '抗', '炎症', '薬', '(', 'NSAIDs', ')', 'で', '、', '\n', '痛み', 'や', '発熱', '、', '炎症', 'を', '抑える', '効果', 'が', 'あり', 'ます', '。', '具体', '的', 'に', 'は', '、', '関節', 'リウマチ', '、', '変形', '性', '関節', '症', '、', '腰痛', '症', '、', '肩こり', '、', '\n', '歯痛', '、', '手術', '後', 'や', '外傷', '後', 'の', '炎症', 'や', '痛み', '、', '風邪', 'による', '熱', 'や', '痛み', 'など', 'に', '用い', 'られ', 'ます', '。']

Cleaned (45): ['ロキソニン', '錠', '説明', '書', 'ね', 'ロキソニン', '解熱', '鎮痛', '作用', '非', 'ステロイド', '性', '抗', '炎症', '薬', '痛み', '発熱', '炎症', '抑える', '効果', 'あり', '具体', '的', '関節', 'リウマチ', '変形', '性', '関節', '症', '腰痛', '症', '肩こり', '歯痛', '手術', '後', '外傷', '後', '炎症', '痛み', '風邪', '熱', '痛み', 'など', '用い', 'られ']

5. OpenAI GPT-4o
Raw (40): ['ロキソニン錠', 'の', '説明書', 'です', 'ね', '。', 'ロキソニン', 'は', '、', '解熱鎮痛作用', 'の', 'ある', '非ステロイド性抗炎症薬', '(', 'NSAIDs', ')', 'で', '、', '痛み', 'や', '発熱', '、', '炎症', 'を', '抑える', '効果', 'が', 'あります', '。', '具体的', 'に', 'は', '、', '関節リウマチ', '、', '変形性関節症', '、', '腰痛症', '、', '肩こり']

Cleaned (17): ['ロキソニン錠', '説明書', 'ね', 'ロキソニン', '解熱鎮痛作用', '非ステロイド性抗炎症薬', '痛み', '発熱', '炎症', '抑える', '効果', 'あります', '具体的', '関節リウマチ', '変形性関節症', '腰痛症', '肩こり']

分析結果サマリー・統計表

トークン化結果を定量的に分析し、各アナライザーの特性を明確にします。

サマリーテーブル内容:

1. トークン数比較表

  • Raw Tokens: 各アナライザーの生トークン数
  • Cleaned Tokens: クリーニング後のトークン数
  • Effectiveness: クリーニング効果率(ノイズ除去率)

2. ユニークトークン分析

  • 各アナライザー固有のトークン: 他では検出されない独自トークン
  • 全アナライザー共通トークン: すべてで一致する基本トークン
  • トークン多様性: 全体での語彙カバレッジ

分析指標:

  • トークン総数: 各手法の分割粒度の違い
  • 共通度: アナライザー間の一致率
  • 独自性: 各手法の特徴的な分割パターン

活用方法:

  • 検索システム: 共通トークンは検索精度向上に寄与
  • NLP処理: 用途に応じた最適アナライザー選択
  • 品質評価: トークン化の一貫性・信頼性評価
Tokenization Results Summary - Kuromoji Baseline
==========================================================================================
Tokenizer       Raw Tokens   Final Tokens    vs Kuromoji     Note                
------------------------------------------------------------------------------------------
kuromoji        42           42              100.0%          ベースライン              
sudachi_A       86           49              71.4%           クリーニング済み            
sudachi_B       78           41              53.3%           クリーニング済み            
sudachi_C       78           41              53.3%           クリーニング済み            
mecab           80           45              79.5%           クリーニング済み            
janome          82           45              79.5%           クリーニング済み            
openai          40           17              15.9%           クリーニング済み            

上の表の読み解き方;

  • Raw Tokens (生トークン数)
    • アナライザーがテキストを最初に分割した直後のトークン(単語)の総数です。この数値が大きいほど、より細かく単語を分割していることを示します。
  • Final Tokens (最終トークン数)
    • 「生トークン」から助詞(「は」「が」など)、句読点、記号といった検索ノイズになりやすい不要なトークンを取り除いた(クリーニングした)後の数です。このクリーニング処理により、各アナライザーを公平な土俵で比較できるようになります。
  • vs Kuromoji (Kuromojiとの類似度)
    • クリーニング後のトークンセットが、基準であるKuromojiのトークンセットとどれだけ似ているかを示す割合(Jaccard係数)です。
    • 計算式: (両者に共通するトークン数) ÷ (どちらか一方にでも存在するユニークなトークン総数)
    • このパーセンテージが高いほど、そのアナライザーの分割結果がKuromojiと似ていることを意味します。例えば、MeCabとJanomeはクリーニング後に80%の類似度となり、Kuromojiと非常に近い結果を出していることがわかります。
🔍 Unique Tokens Analysis (Cleaned Results vs Kuromoji Baseline)
======================================================================
sudachi_A: ['あり', 'など', 'ね', 'み', 'よる', 'られ', '用い', '痛']
sudachi_B: ['あり', 'など', 'ね', 'よる', 'られ', '具体的', '変形性', '用い', '腰痛症', '説明書', '関節症']
sudachi_C: ['あり', 'など', 'ね', 'よる', 'られ', '具体的', '変形性', '用い', '腰痛症', '説明書', '関節症']
mecab: ['あり', 'など', 'ね', 'られ', '用い']
janome: ['あり', 'など', 'ね', 'られ', '用い']
openai: ['あります', 'ね', 'ロキソニン錠', '具体的', '変形性関節症', '腰痛症', '解熱鎮痛作用', '説明書', '関節リウマチ', '非ステロイド性抗炎症薬']

Kuromoji unique tokens (not found in cleaned versions of others):
kuromoji: ['nsaids', '用いる', '痛む']

Common tokens across all tokenizers (after cleaning):
['ロキソニン', '効果', '抑える', '炎症', '発熱', '肩こり']

📊 統計サマリー:
📊 Kuromojiベースライン総トークン数: 34
📊 全アナライザー共通トークン数: 6
📊 共通度: 17.6%

上記は「各アナライザーのクリーニング後トークン」から「Kuromojiのトークン」を引いた残りのリストです。つまり、Kuromojiにはないが、そのアナライザーだけが生成したユニークなトークンです。各ツールの辞書や分割ルールの違いが見てとれます。

Kuromojiだけが生成したトークンの後に、クリーニング後にすべてのアナライザーが共通して生成したトークンです。これらは、どのツールを使っても分割結果が変わらない、文章の核となる重要な単語と言えます。

考察

Kuromoji / MeCab / Janome

  • 「専門職」→「専門」「職」、「看護師」→「看護」「師」のように、単語を細かく分割します。
  • これにより検索ヒット率が向上し、部分一致検索や強調表示に適しています。

Sudachi

  • 「専門職」や「看護師」などの複合語を1トークンとして保持します。
  • 意味のまとまりを重視する分析に向いており、モード切り替え(A/B/C)で粒度を調整できます。
    • Cモード: 「より良い検索体験を提供したい」が1語扱いになるため、特定の複合語での検索には不向きな場合があります。
    • Bモード: 実用面でバランスの取れた粒度を提供します。

LLM(GPT-4o)

  • 文脈理解に基づいた分かち書きが可能です。
  • 「介護福祉士」や「認知症」など、語彙として自然なまとまりで出力されます。
  • トークンの一貫性がないため、Elasticsearchのインデックス用途には不向きですが、意味理解や質問応答に最適です。

まとめ

日本語検索において、どのアナライザーを使うかは「検索したい内容」と「求める粒度」によって変わります。

  • 細かく一致させたいなら Kuromoji
  • 検索文をそのまま一致させたいなら Sudachi Cモード
  • バランス重視なら Sudachi Bモード