Elasticsearchの標準アナライザーは Kuromoji ですが、他にも日本語向けのアナライザーが存在します。本記事では Sudachi や MeCab 、およびPythonライブラリの Janome 、そして LLM(GPT-4) といった選択肢を比較し、どんな場面でどれを使うべきかを検討しました。 なお、Elasticsearch 9.xではSudachiやMeCabの公式対応プラグインはまだリリースされていません。そのため今回は Python環境で事前に形態素解析してからElasticsearchにインデックスする方法 を採用しています。一方、Janomeは純Python実装であるため、追加プラグインなしで使用可能です。 目次 比較対象アナライザー ステップ0:事前準備 Python環境とテキスト設定 トークン正規化・クリーニング関数 比較戦略 トークン化関数の定義(共通フォーマット出力) 包括的トークナイザー比較分析 📊 Kuromoji(ベースライン)出力 📊 Sudachi A/B/C、MeCab、Janome、GPT-4 出力 分析結果サマリー・統計表 1. トークン数比較表 2. ユニークトークン分析 考察 まとめ 比較対象アナライザー 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モード The post 日本語アナライザーの比較:Kuromoji・Sudachi・MeCab・Janome・LLMの性能検証 first appeared on Elastic Portal .