
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モード