検索技術の基礎
検索エンジンを理解するために必要な基本概念を解説します。これらの概念は、Solr、Elasticsearch、Meilisearchなどの検索エンジンや、Fuse.js、Lunr.jsなどのクライアントサイド検索ライブラリを理解・選定する際の基礎知識となります。
転置インデックス(Inverted Index)
検索エンジンが高速な検索を実現する最も重要な基盤技術です。
従来のアプローチ(全件走査)
文書1: "Astroは静的サイトジェネレーターです"
文書2: "Reactは人気のUIライブラリです"
文書3: "AstroはReactコンポーネントを統合できます"
検索「Astro」→ 全文書を順番にスキャン → O(n)で遅い
転置インデックス(逆引き)
"Astro" → [文書1, 文書3]
"React" → [文書2, 文書3]
"静的サイト" → [文書1]
"UI" → [文書2]
単語から文書への逆引きインデックスを事前に構築することで、「Astro」を検索すると瞬時に文書1と文書3が特定できます。計算量はO(1)に近くなります。
転置インデックスの構造
単語 → [(文書ID, 出現位置, 出現回数), ...]
例:
"Astro" → [
(doc1, [0], 1), # 文書1の0番目の位置に1回出現
(doc3, [0], 1) # 文書3の0番目の位置に1回出現
]
出現位置を保存することで、フレーズ検索(「静的 サイト ジェネレーター」のような連続した単語の検索)も可能になります。
トークナイザー(Tokenizer)
テキストを検索可能な単位(トークン)に分割する処理です。
英語の場合(スペース区切り)
"Hello World" → ["Hello", "World"]
"user-friendly" → ["user", "friendly"] or ["user-friendly"]
英語はスペースで単語が区切られているため、比較的シンプルです。
日本語の場合(形態素解析が必要)
"東京都に住んでいます" → ["東京", "都", "に", "住ん", "で", "い", "ます"]
日本語はスペースで区切られていないため、単語の境界を判定する形態素解析が必要です。
トークナイザーの処理フロー
入力テキスト
↓
トークン化(分割)
↓
正規化(小文字化、Unicode正規化)
↓
フィルタリング(ストップワード除去)
↓
ステミング/レンマ化(語幹抽出)
↓
インデックスに格納
形態素解析(Morphological Analysis)
日本語テキストを品詞ごとに分解する処理です。辞書ベースで単語を認識し、品詞情報も付与します。
形態素解析の例
入力: "東京都に住んでいます"
出力:
東京 名詞-固有名詞-地域-一般
都 名詞-接尾-地域
に 助詞-格助詞-一般
住ん 動詞-自立(住む)
で 助詞-接続助詞
い 動詞-非自立
ます 助動詞
主要な形態素解析ライブラリ
| ライブラリ | 言語 | 特徴 | 使用される検索エンジン |
|---|---|---|---|
| MeCab | C++ | 高速、カスタマイズ性高い、定番 | 多くのシステム |
| Kuromoji | Java | Java環境向け、Lucene統合 | Solr, Elasticsearch |
| Sudachi | Java | 複数の分割粒度、辞書が充実 | 企業システム |
| Lindera | Rust | Rust環境向け、軽量 | Meilisearch |
| Janome | Python | Pure Python、導入が容易 | Python環境 |
分割粒度の違い(Sudachiの例)
入力: "国立国会図書館"
A(短い単位): 国立 / 国会 / 図書館
B(中間単位): 国立 / 国会図書館
C(長い単位): 国立国会図書館
用途によって適切な分割粒度を選択します。検索では短い単位(A)が一般的です。
N-gram
文字列をN文字ずつ分割する手法です。形態素解析不要で日本語対応が可能。
N-gramの例
入力: "東京都"
1-gram(ユニグラム): ["東", "京", "都"]
2-gram(バイグラム): ["東京", "京都"]
3-gram(トライグラム): ["東京都"]
形態素解析との比較
| 観点 | 形態素解析 | N-gram |
|---|---|---|
| 辞書 | 必要 | 不要 |
| 未知語 | 弱い(辞書にない単語) | 強い(任意の文字列) |
| インデックスサイズ | 小さい | 大きい |
| 検索精度 | 高い | ノイズが多い |
| 処理速度 | やや遅い | 速い |
N-gramの注意点
"東京都" の2-gram → ["東京", "京都"]
「京都」で検索すると「東京都」もヒットしてしまう(ノイズ)
この問題を軽減するため、形態素解析とN-gramを組み合わせるハイブリッドアプローチも使われます。
スコアリングアルゴリズム
検索結果の関連度を計算し、ランキングする手法です。
TF-IDF(Term Frequency - Inverse Document Frequency)
単語の重要度を「出現頻度」と「希少性」から算出する古典的な手法です。
TF(Term Frequency / 出現頻度)
その文書内でどれだけ出現するか:
TF = 文書内での単語の出現回数 / 文書内の総単語数
例: 文書に「Astro」が3回、総単語数100の場合
TF = 3 / 100 = 0.03
IDF(Inverse Document Frequency / 逆文書頻度)
全文書中でどれだけ珍しいか:
IDF = log(総文書数 / その単語を含む文書数)
例: 総文書数1000、「Astro」を含む文書が10の場合
IDF = log(1000 / 10) = log(100) ≈ 2.0
例: 総文書数1000、「the」を含む文書が900の場合
IDF = log(1000 / 900) ≈ 0.05
TF-IDFスコア
TF-IDF = TF × IDF
「Astro」: 0.03 × 2.0 = 0.06(重要度高)
「the」 : 0.05 × 0.05 = 0.0025(重要度低)
頻出する一般的な単語(the, is, a等)は重要度が低く、特定のトピックに関連する単語は重要度が高くなります。
BM25(Best Matching 25)
TF-IDFを改良した現代の標準的なランキングアルゴリズムです。Elasticsearch、Solr、Meilisearchなどで採用されています。
TF-IDFの問題点
- 単語が何度も出現すると、スコアが際限なく上がる
- 文書の長さを考慮していない(長い文書が有利)
BM25の改良点
BM25 = IDF × (TF × (k1 + 1)) / (TF + k1 × (1 - b + b × (文書長 / 平均文書長)))
パラメータ:
- k1: TFの飽和度(通常1.2〜2.0)
- b: 文書長の正規化(通常0.75)
- TFの飽和: 単語の出現回数が増えても、スコアの増加が頭打ちになる
- 文書長の正規化: 長い文書と短い文書を公平に比較
ファジー検索(Fuzzy Search)
タイプミスや表記ゆれを許容する検索です。
レーベンシュタイン距離(編集距離)
2つの文字列間の「最小編集回数」を計算します。編集操作は以下の3種類:
- 挿入: 文字を追加
- 削除: 文字を削除
- 置換: 文字を別の文字に変更
"Astro" と "Astor" の編集距離:
Astro → Astor(rとoを入れ替え = 置換2回、または削除+挿入)
編集距離 = 2
"kitten" と "sitting" の編集距離:
kitten → sitten(k→s置換)→ sittin(e→i置換)→ sitting(g挿入)
編集距離 = 3
ファジー検索の実装
// 編集距離が閾値以下なら検索結果に含める
function fuzzySearch(query, target, maxDistance = 2) {
const distance = levenshteinDistance(query, target);
return distance <= maxDistance;
}
// "Astor" で検索 → "Astro" もヒット(編集距離2以下)
表記ゆれの対応
ファジー検索は以下のような表記ゆれにも対応できます:
検索: "リアクト" → "React" もヒット(同義語辞書併用)
検索: "JavaScript" → "JavaScript" もヒット(Unicode正規化)
検索: "colour" → "color" もヒット(異体字対応)
ファセット検索
カテゴリやタグなどの属性で検索結果を絞り込む機能です。ECサイトでよく見られます。
ファセット検索の例
検索: "ノートパソコン"
検索結果: 150件
絞り込み条件(ファセット):
├── メーカー
│ ├── Apple (25)
│ ├── Dell (30)
│ ├── Lenovo (40)
│ └── HP (55)
├── 価格帯
│ ├── 〜5万円 (20)
│ ├── 5〜10万円 (50)
│ ├── 10〜20万円 (60)
│ └── 20万円〜 (20)
├── 画面サイズ
│ ├── 13インチ (45)
│ ├── 14インチ (50)
│ └── 15インチ以上 (55)
└── OS
├── Windows (100)
└── macOS (50)
ファセット検索の実装
検索エンジンでは、フィールドごとに値の集計(アグリゲーション)を行います:
// Elasticsearchでのファセット検索例
{
"query": { "match": { "title": "ノートパソコン" } },
"aggs": {
"メーカー": { "terms": { "field": "manufacturer" } },
"価格帯": { "range": { "field": "price", "ranges": [
{ "to": 50000 },
{ "from": 50000, "to": 100000 },
{ "from": 100000 }
]}}
}
}
ベクトル検索(Vector Search)
テキストを数値ベクトル(埋め込み / Embedding)に変換し、意味的な類似性で検索する技術です。
従来のキーワード検索の限界
検索: "犬のしつけ"
結果: 「犬のしつけ」という単語を含む文書のみ
→ 「ペットのトレーニング」「子犬の教育」はヒットしない
ベクトル検索の仕組み
- 埋め込み(Embedding): テキストを数値ベクトルに変換
"犬" → [0.2, 0.8, 0.1, 0.5, ...] (数百〜数千次元)
"猫" → [0.3, 0.7, 0.2, 0.4, ...] ← "犬"に近い
"車" → [0.9, 0.1, 0.8, 0.2, ...] ← "犬"から遠い
- 類似度計算: コサイン類似度などでベクトル間の距離を計算
コサイン類似度 = (A · B) / (|A| × |B|)
"犬" と "猫" の類似度: 0.95(高い)
"犬" と "車" の類似度: 0.15(低い)
- 検索: クエリをベクトル化し、類似ベクトルを持つ文書を検索
埋め込みモデル
| モデル | 提供元 | 特徴 |
|---|---|---|
| text-embedding-3-small | OpenAI | 高品質、API利用 |
| sentence-transformers | Hugging Face | オープンソース、多言語対応 |
| Cohere Embed | Cohere | 多言語対応、API利用 |
| E5 | Microsoft | 多言語対応、オープンソース |
ベクトルデータベース
| データベース | 特徴 |
|---|---|
| Pinecone | マネージドサービス、使いやすい |
| Weaviate | オープンソース、GraphQL API |
| Qdrant | Rust製、高速 |
| Chroma | 軽量、ローカル開発向け |
| pgvector | PostgreSQL拡張、既存DBと統合 |
| Elasticsearch | 8.0以降でベクトル検索対応 |
ハイブリッド検索
キーワード検索とベクトル検索を組み合わせる手法:
最終スコア = α × キーワード検索スコア + (1-α) × ベクトル検索スコア
α = 0.5 で両方を均等に重み付け
キーワードの正確なマッチと、意味的な類似性の両方を活用できます。
ストップワード(Stop Words)
検索に有用でない一般的な単語を除外する処理です。
ストップワードの例
英語: the, a, an, is, are, was, were, be, been, being, have, has, had, do, does, did, will, would, could, should, may, might, must, shall, can, need, dare, ought, used, to, of, in, for, on, with, at, by, from, as, into, through, during, before, after, above, below, between, under, again, further, then, once, here, there, when, where, why, how, all, each, few, more, most, other, some, such, no, nor, not, only, own, same, so, than, too, very, just, also, now
日本語: の, に, は, を, た, が, で, て, と, し, れ, さ, ある, いる, も, する, から, な, こと, として, い, や, など, なっ, ない, この, ため, その, あっ, よう, また, もの, という, あり, まで, られ, なる, へ, か, だ, これ, によって, により, おり, より, による, ず, なり, られる, において, ば, なかっ, なく, しかし, について, せ, だっ, その後, できる, それ, う, ので, なお, のみ, でき, き, つ, における, および, いう, さらに, でも, ら, たり, その他, に関する, たち, ます, ん, なら
ストップワード除去の効果
入力: "Astroは静的サイトジェネレーターです"
ストップワード除去前: ["Astro", "は", "静的", "サイト", "ジェネレーター", "です"]
ストップワード除去後: ["Astro", "静的", "サイト", "ジェネレーター"]
インデックスサイズの削減と検索精度の向上に寄与します。
ステミング / レンマ化
単語を基本形に変換する処理です。
ステミング(Stemming)
語尾を機械的に切り落とす:
running → run
cats → cat
better → better(不規則変化は対応しない場合あり)
アルゴリズム例: Porter Stemmer, Snowball Stemmer
レンマ化(Lemmatization)
辞書を使って正しい基本形に変換:
running → run
ran → run
better → good
cats → cat
より正確だが、処理が重くなります。
日本語の場合
形態素解析で基本形(原形)を取得:
"住んでいます" → 形態素解析 → "住む"(基本形)
"走った" → "走る"
参考資料
- Introduction to Information Retrieval - スタンフォード大学の情報検索教科書
- Elasticsearch: The Definitive Guide
- Apache Lucene - Scoring
- BM25 - Wikipedia
- MeCab公式サイト
- Sentence Transformers
関連トピック
- 検索エンジン選定ガイド - 検索エンジンの技術選定
- Apache Solr - エンタープライズ検索エンジン
- pgvector - PostgreSQLのベクトル検索拡張
- Supabase + pgvector - Supabaseでセマンティック検索
- Embedding APIの選択 - 埋め込み生成APIの選択肢
- セマンティック検索の実装パターン - 実装パターン詳細