検索技術の基礎

作成日:
search-engine full-text-search algorithm information-retrieval

検索エンジンを理解するために必要な基本概念を解説します。これらの概念は、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)

日本語テキストを品詞ごとに分解する処理です。辞書ベースで単語を認識し、品詞情報も付与します。

形態素解析の例

入力: "東京都に住んでいます"

出力:
東京  名詞-固有名詞-地域-一般
都    名詞-接尾-地域
に    助詞-格助詞-一般
住ん  動詞-自立(住む)
で    助詞-接続助詞
い    動詞-非自立
ます  助動詞

主要な形態素解析ライブラリ

ライブラリ言語特徴使用される検索エンジン
MeCabC++高速、カスタマイズ性高い、定番多くのシステム
KuromojiJavaJava環境向け、Lucene統合Solr, Elasticsearch
SudachiJava複数の分割粒度、辞書が充実企業システム
LinderaRustRust環境向け、軽量Meilisearch
JanomePythonPure 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の飽和: 単語の出現回数が増えても、スコアの増加が頭打ちになる
  • 文書長の正規化: 長い文書と短い文書を公平に比較

タイプミスや表記ゆれを許容する検索です。

レーベンシュタイン距離(編集距離)

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 }
    ]}}
  }
}

テキストを数値ベクトル(埋め込み / Embedding)に変換し、意味的な類似性で検索する技術です。

従来のキーワード検索の限界

検索: "犬のしつけ"
結果: 「犬のしつけ」という単語を含む文書のみ

→ 「ペットのトレーニング」「子犬の教育」はヒットしない

ベクトル検索の仕組み

  1. 埋め込み(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, ...]  ← "犬"から遠い
  1. 類似度計算: コサイン類似度などでベクトル間の距離を計算
コサイン類似度 = (A · B) / (|A| × |B|)

"犬" と "猫" の類似度: 0.95(高い)
"犬" と "車" の類似度: 0.15(低い)
  1. 検索: クエリをベクトル化し、類似ベクトルを持つ文書を検索

埋め込みモデル

モデル提供元特徴
text-embedding-3-smallOpenAI高品質、API利用
sentence-transformersHugging Faceオープンソース、多言語対応
Cohere EmbedCohere多言語対応、API利用
E5Microsoft多言語対応、オープンソース

ベクトルデータベース

データベース特徴
Pineconeマネージドサービス、使いやすい
Weaviateオープンソース、GraphQL API
QdrantRust製、高速
Chroma軽量、ローカル開発向け
pgvectorPostgreSQL拡張、既存DBと統合
Elasticsearch8.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

より正確だが、処理が重くなります。

日本語の場合

形態素解析で基本形(原形)を取得:

"住んでいます" → 形態素解析 → "住む"(基本形)
"走った" → "走る"

参考資料

関連トピック