次の方法で共有


Azure AI Search でのフルテキスト検索

フルテキスト検索は、インデックスに格納されているプレーン テキストと照合する情報取得のアプローチです。 たとえば、クエリ文字列 "hotels in San Diego on the beach" を指定すると、検索エンジンはそれらの用語に基づいてトークン化された文字列を検索します。 スキャンを効率化するために、クエリ文字列の字句解析が行われます。つまり、すべての用語の小文字化、"the" などのストップ ワードの削除、プリミティブな原形への用語の単純化が行われます。 一致する語句が見つかると、検索エンジンにより、ドキュメントが取得され、関連度の順でドキュメントがランク付けされて上位の結果が返されます。

クエリの実行は複雑な場合があります。 この記事は、Azure AI Search におけるフルテキスト検索のしくみについて理解を深める必要がある開発者を対象としています。 テキスト クエリの場合、Azure AI Search はほとんどのシナリオで予想される結果をシームレスに提供しますが、場合によっては、何らかの方法で "オフ" と思われる結果が得られる場合があります。 このような状況では、Lucene クエリ実行の 4 つの段階 (クエリ解析、字句解析、ドキュメント照合、スコアリング) の背景を持つことで、目的の結果を生成するクエリ パラメーターまたはインデックス構成に対する特定の変更を特定できます。

Note

Azure AI Search ではフルテキスト検索に Apache Lucene が使用されますが、Lucene の統合は完全ではありません。 Microsoft は、Azure AI Search にとって重要なシナリオを実現する Lucene の機能を選んで公開、拡張しています。

アーキテクチャの概要と図

クエリの実行には次の 4 つの段階があります。

  1. クエリ解析
  2. 字句解析
  3. 文書検索
  4. ポイントの計算

フルテキスト検索クエリは、クエリ テキストを解析して検索語と演算子を抽出することから始まります。 2 つのパーサーがあり、速度と複雑さを選択できます。 次に分析段階では、個々の検索語がしばしば分解され、新しい形に再構築されます。 この手順では、できるだけ広い範囲から "一致と見なす候補" が得られます。 検索エンジンは、インデックスをスキャンして、一致する語句を持つドキュメントを検索し、各一致をスコア付けします。 その後、一致する個々の文書に割り当てられた関連度スコアに基づいて結果セットが並べ替えられます。 このようにランク順に並んだリストの先頭の結果が、呼び出し元のアプリケーションに返されます。

次の図は、検索要求の処理に使用されるコンポーネントを示しています。

Azure AI 検索における Lucene クエリ アーキテクチャのダイアグラム。

主なコンポーネント 機能の説明
クエリ パーサー クエリ演算子から検索語を切り離し、検索エンジンに送るクエリ構造 (クエリ ツリー) を作成します。
アナライザー 検索語に対する字句解析を実行します。 このプロセスには、クエリ用語の変換、削除、または展開が含まれる場合があります。
Index 索引付けの対象文書から抽出した検索可能な語句を効率よく体系的に格納するデータ構造です。
検索エンジン 転置インデックスの内容に基づいて、一致する文書を検索してスコア付けします。

検索要求の構造

検索要求は、結果セットで返すべき内容を詳細に規定した仕様です。 最も単純な形式では、任意の種類の条件を持たない空のクエリです。 しかしより現実的な例では、パラメーターや複数の検索語を伴うのが一般的です。場合によっては、検索範囲を特定のフィールドに限定したり、フィルター式や並べ替え規則が使われたりすることもあります。

次の例は、 REST API を使用して Azure AI Search に送信する検索要求です。

POST /indexes/hotels/docs/search?api-version=2025-09-01
{
    "search": "Spacious, air-condition* +\"Ocean view\"",
    "searchFields": "description, title",
    "searchMode": "any",
    "filter": "price ge 60 and price lt 300",
    "orderby": "geo.distance(___location, geography'POINT(-159.476235 22.227659)')", 
    "queryType": "full" 
}

この要求に対して、検索エンジンは次の操作を実行します。

  1. 価格が $60 以上 $300 未満の文書を検索します。

  2. クエリを実行します。 この例では、検索クエリは語句と用語で構成されています: "Spacious, air-condition* +\"Ocean view\"" (ユーザーは通常、句読点を入力しませんが、この例に含めることで、アナライザーがそれを処理する方法を説明できます)。

    このクエリの場合、検索エンジンは、"searchFields" に指定された説明フィールドとタイトル フィールドをスキャンして、"Ocean view" を含む文書、さらに "spacious" という語句、または "air-condition" というプレフィックスで始まる語句を探します。 明示的に必須指定 (+) されていない語句に関して、マッチング対象を任意 (既定) とするか、すべてとするかが、"searchMode" パラメーターで指定されています。

  3. 結果として得られた一連のホテルを特定の地理的位置に近い順に並べ替え、結果を呼び出し元のアプリケーションに返します。

この記事の大部分は、 検索クエリの処理についてです: "Spacious, air-condition* +\"Ocean view\""。 フィルター処理と並べ替えについては取り上げません。 詳細については、Search API のリファレンス ドキュメントを参照してください。

第 1 段階: クエリ解析

前出のとおり、検索要求の最初の行がクエリ文字列です。

 "search": "Spacious, air-condition* +\"Ocean view\"", 

クエリ パーサーは、演算子 (例では *+ など) を検索用語から分離し、検索クエリをサポートされている型の サブクエリ に分解します。

  • "単語検索": 独立した語を検索 (spacious など)
  • "フレーズ検索": 引用符で囲まれた語句を検索 (ocean view など)
  • "プレフィックス検索": 語句 + プレフィックス演算子 *を検索 (air-condition など)

サポートされているクエリの種類の完全な一覧については、 Lucene クエリ構文に関するページを参照してください。

文書が一致していると見なされるために検索条件との一致が "必須 (must)" であるのか "勧告 (should be)" であるのかは、サブクエリに関連付けられている演算子によって決まります。 たとえば、+"Ocean view"+ 演算子が指定されているので検索条件との一致が "必須" となります。

クエリ パーサーは、サブクエリを クエリ ツリー (クエリ を表す内部構造) に再構築し、検索エンジンに渡します。 クエリ解析の最初の段階では、クエリ ツリーは次のようになります。

searchmode が any に設定されたブール型クエリの概念図。

サポートされるパーサー: Simple と Full Lucene

Azure AI Search では、 simple (既定) と fullという 2 つの異なるクエリ言語が公開されています。 どちらのクエリ言語を使うかは、検索要求で queryType パラメーターの設定で指定します。その指定に基づいて、クエリ パーサーが演算子と構文を解釈します。

  • Simple クエリ言語は直感的で堅牢であり、多くの場合、クライアント側の処理なしでユーザー入力 as-is を解釈するのに適しています。 Web 検索エンジンで多く使われているクエリ演算子がサポートされます。

  • Full Lucene クエリ言語は、queryType=full を設定することによって利用できます。より多くの演算子やクエリの種類 (ワイルドカード、あいまい一致、正規表現、フィールド指定検索など) がサポートされることで、既定の Simple クエリ言語が拡張されます。 たとえば、Simple クエリ構文で送信された正規表現は、式としてではなくクエリ文字列と解釈されます。 この記事で紹介している要求の例では、Full Lucene クエリ言語を使用しています。

searchMode がパーサーに及ぼす影響

解析方法に作用する検索要求パラメーターとしては、他にも "searchMode" パラメーターがあります。 これは、ブール クエリの既定の演算子、つまり any (既定) または all を制御します。

"searchMode=any" とした場合 (既定)、spacious と air-condition との間に区切り記号として置かれた空白は OR (||) の働きをするため、サンプル クエリ テキストは次のクエリ テキストに相当します。

Spacious,||air-condition*+"Ocean view" 

ブール クエリの構造において、明示的な演算子 (+"Ocean view"+ など) の意味ははっきりしています。つまり検索条件との一致は "必須 (must)" です。 それに比べて、残りの語句 (spacious と air-condition) の解釈はあいまいです。 検索エンジンが探すべきなのは、ocean view spacious air-condition の "すべて" との一致でしょうか。 それとも、ocean view に加えて、残りの 2 つの語句のうち、"どちらか一方" のみが含まれていればよいのでしょうか。

既定 ("searchMode=any") では、検索エンジンはより広い解釈を想定します。 どちらか一方のフィールドが一致していればよい (should) の意味、つまり "or" のセマンティクスで解釈されます。 先ほど例に挙げた、2 つの "should" 演算を含んだクエリ ツリーは既定の動作を示しています。

では、"searchMode=all" と設定したらどうなるでしょうか。 この場合は、空白文字が "and" 演算と解釈されます。 一致として認められるには、残りの2つの用語が文書内に存在する必要があります。 結果のサンプル クエリは次のように解釈されます。

+Spacious,+air-condition*+"Ocean view"

一致するドキュメントが 3 つのサブクエリすべての共通部分である、このクエリの変更されたクエリ ツリーは次のようになります。

searchmode が all に設定されたブール型クエリの概念図。

Note

"searchMode=all" の上に "searchMode=any" を選択することは、代表的なクエリを実行することで最適な判断です。 普段から演算子を指定するユーザー (ドキュメント ストアを検索するときなど) は、"searchMode=all"で得られるブール クエリの構造の方が直感的にわかりやすいかもしれません。 "searchMode" と演算子の間の相互作用の詳細については、「 単純なクエリ構文」を参照してください。

第 2 段階: 字句解析

クエリ ツリーが構築された後、"単語検索" と "フレーズ検索" のクエリがアナライザーによって加工されます。 アナライザーは、パーサーから渡されたテキスト入力を受け取ってそのテキストを加工してから、トークン化した語句をクエリ ツリーに組み入れます。

構文分析の最も一般的な形式は 言語分析であり、特定の言語に固有のルールに基づいてクエリ用語を変換します。 これには次の処理が含まれます。

  • クエリ用語を単語の語幹に変換する。
  • 必須ではない単語 (英語の "the" や "and" などのストップワード) を削除する。
  • 複合単語をコンポーネント パーツに分割する。
  • 大文字の単語を小文字にします。

通常はこれらの操作をひととおり適用することで、ユーザーによって入力されたテキストと、インデックスに格納されている語句との相違点が取り除かれます。 こうした操作はテキスト処理の範囲を超えており、言語そのものに対する深い知識が必要となります。 この言語知識のレイヤーを追加するために、Azure AI Search は、Lucene と Microsoft から提供されているさまざまな言語アナライザーに対応しています。

Note

シナリオによっては、分析の要件は最小限から複雑なものまでさまざまです。 構文分析の複雑さを制御するには、定義済みのアナライザーのいずれかを選択するか、独自の カスタム アナライザーを作成します。 アナライザーは、検索可能なフィールドにその適用対象が限定されており、フィールド定義の一環として指定されます。 これにより、フィールドごとに多様な字句解析を行うことができます。 指定しない場合は、 標準 の Lucene アナライザーが使用されます。

この例では、解析前の最初のクエリ ツリーに "Spacious," という語句があり、大文字の "S" とコンマはクエリ パーサーによって検索語の一部として解釈されています (コンマはクエリ言語の演算子とは見なされません)。

既定のアナライザーが用語を処理すると、"ocean view" と "spacious" が小文字になり、コンマ文字が削除されます。 変更されたクエリ ツリーは次のようになります。

分析された用語を含むブール型クエリの概念図。

アナライザーの動作テスト

アナライザーの動作は、Analyze API を使ってテストすることができます。 分析するテキストを指定して、指定されたアナライザーによって生成される用語を確認します。 たとえば、標準アナライザーで "air-condition" というテキストがどのように加工されるかを確認するには、次のように要求します。

{
    "text": "air-condition",
    "analyzer": "standard"
}

標準アナライザーは、入力テキストを次の 2 つのトークンに分解します。その際、開始オフセットと終了オフセット (一致部分の強調表示に使用される) やその位置 (フレーズの照合に使用される) など、各種の属性を使ってそれらに注釈を付けます。

{
  "tokens": [
    {
      "token": "air",
      "startOffset": 0,
      "endOffset": 3,
      "position": 0
    },
    {
      "token": "condition",
      "startOffset": 4,
      "endOffset": 13,
      "position": 1
    }
  ]
}

字句解析の例外

字句分析は、用語クエリまたはフレーズ クエリのいずれか、完全な用語を必要とするクエリの種類にのみ適用されます。 プレフィックス クエリ、ワイルドカード クエリ、正規表現クエリなど、不完全な用語を含むクエリの種類やあいまいクエリには適用されません。 この例のプレフィックス クエリと air-condition* という用語を含むクエリの種類は、分析ステージをバイパスしてクエリ ツリーに直接追加されます。 こうした種類の検索語に対して適用される変換は、大文字から小文字への変換だけです。

第 3 段階: 文書検索

ここでいう文書検索とは、一致する語句がインデックスに存在する文書を見つけることです。 この段階は、例を通じて最もよく理解されています。 次の単純なスキーマを持つホテル インデックスから始めましょう。

{
    "name": "hotels",
    "fields": [
        { "name": "id", "type": "Edm.String", "key": true, "searchable": false },
        { "name": "title", "type": "Edm.String", "searchable": true },
        { "name": "description", "type": "Edm.String", "searchable": true }
    ] 
} 

さらに、このインデックスには、以下の 4 つの文書が追加されているとします。

{
    "value": [
        {
            "id": "1",
            "title": "Hotel Atman",
            "description": "Spacious rooms, ocean view, walking distance to the beach."
        },
        {
            "id": "2",
            "title": "Beach Resort",
            "description": "Located on the north shore of the island of Kauaʻi. Ocean view."
        },
        {
            "id": "3",
            "title": "Playa Hotel",
            "description": "Comfortable, air-conditioned rooms with ocean view."
        },
        {
            "id": "4",
            "title": "Ocean Retreat",
            "description": "Quiet and secluded"
        }
    ]
}

用語のインデックス付け方法

検索を理解するには、インデックス作成の基本をいくつかを把握しておくと役立ちます。 保存の単位は転置インデックスで、検索可能なフィールドごとに 1 つ存在します。 転置インデックス内には、全文書から抽出されたすべての語句を並べ替えたリストが存在します。 それぞれの語句は、それが出現する一連の文書に対応付けられています (以下の例を参照)。

転置インデックスに含める語句を得るために、検索エンジンは、クエリの加工時と同様の字句解析を文書の内容に対して実行します。

  1. テキスト入力 は、アナライザーの構成に応じて、すべて小文字に変換され、句読点が削除されるなどの処理が行われます。
  2. "トークン" は字句解析の出力です。
  3. "用語" はインデックスに追加されます。

検索語の体裁をインデックスに登録されている語句の体裁と合わせるために、通常は検索操作とインデックス作成操作に同じアナライザーが使用されますが、必ずしも同じである必要はありません。

Note

Azure AI Search では、追加の indexAnalyzer および searchAnalyzer フィールド パラメーターを使用して、インデックス作成と検索に別々のアナライザーを指定することができます。 指定しなかった場合、analyzer プロパティで設定されたアナライザーが、インデックス作成と検索の両方に使用されます。

ドキュメントなどの逆インデックス

もう一度先ほどの例を見てみましょう。title フィールドの転置インデックスは、次のようになります。

期間 文書リスト
atman 1
beach 2
hotel 1、3
ocean 4
playa 3
resort 2
retreat 4

タイトル フィールドには、1 と 3 の 2 つのドキュメントに ホテル のみが表示されます。

説明フィールドのインデックスは次のようになります。

期間 文書リスト
air 3
and 4
beach 1
conditioned 3
comfortable 3
distance 1
island 2
kauaʻi 2
located 2
north 2
ocean 1, 2, 3
of 2
on 2
quiet 4
rooms 1、3
secluded 4
shore 2
spacious 1
the 1、2
to 1
ビュー 1, 2, 3
walking 1
with 3

インデックス付き用語に対するクエリ用語の照合

上記の転置インデックスを踏まえてサンプル クエリに戻り、一致する文書がどのように検索されるかを見ていきましょう。 最終的なクエリ ツリーは、次のようになっていたことを思い出してください。

分析された用語を含むブール型クエリの概念図。

クエリの実行中、検索可能なフィールドに対して個々の検索が別々に実行されます。

  • 単語検索 "spacious" と一致する文書は 1 件です (Hotel Atman)。

  • プレフィックス検索 "air-condition*" と一致する文書はありません。

    この動作により、開発者が混乱することがあります。 "air-conditioned" という語句はこの文書内に存在しますが、既定のアナライザーによって 2 つの単語に分割されています。 部分的な語句を含むプレフィックス クエリは、解析されないことに注意してください。 したがって、プレフィックス "air-condition" の用語は逆インデックスで検索され、見つかりません。

  • フレーズ検索 "ocean view" では、"ocean" と "view" という 2 つの語句が検索され、元の文書内で両者が近いかどうかがチェックされます。 ドキュメント 1、2、および 3 は、説明フィールドのこのクエリと一致します。 ドキュメント 4 にはタイトルに "ocean" という用語がありますが、個々の単語ではなく "ocean view" フレーズを探しているため、一致とは見なされません。

Note

特定のフィールドを searchFields パラメーター (検索要求の例を参照) で指定しない限り、検索クエリは、Azure AI Search インデックス内の検索可能なすべてのフィールドに対して個別に実行されます。 選択したフィールドのいずれかで一致する文書が返されます。

全体として、対象のクエリの場合、一致するドキュメントは 1、2、3 です。

第 4 段階: スコア付け

検索結果セット内のすべての文書には、関連度スコアが割り当てられます。 関連度スコアの機能は、ユーザーからの問い合わせ (検索クエリ) に対して最適解となる文書に対し、相対的に高いランクを与えることです。 このスコアは、一致した語句の統計学的な特性に基づいて計算されます。 スコア付け式の中核となるのは、 用語の頻度 -逆ドキュメントの頻度 (TF/IDF) です。 TF/IDF は、出現頻度の低い語句と高い語句を含んだ検索において、出現頻度の低い語句を含んだ結果に、より高いランクを与えます。 たとえば、Wikipedia の記事をすべて含んだ架空のインデックスでは、the president というクエリに一致した文書のうち、president で一致した文書の方が the で一致した文書よりも関連性が高いと見なされます。

スコア付けの例

冒頭のサンプル クエリで見つかった 3 つの文書を思い出してください。

search=Spacious, air-condition* +"Ocean view"  
{
  "value": [
    {
      "@search.score": 0.25610128,
      "id": "1",
      "title": "Hotel Atman",
      "description": "Spacious rooms, ocean view, walking distance to the beach."
    },
    {
      "@search.score": 0.08951007,
      "id": "3",
      "title": "Playa Hotel",
      "description": "Comfortable, air-conditioned rooms with ocean view."
    },
    {
      "@search.score": 0.05967338,
      "id": "2",
      "title": "Ocean Resort",
      "description": "Located on a cliff on the north shore of the island of Kauai. Ocean view."
    }
  ]
}

文書 1 は、クエリに対して最も高い関連度で一致しています。なぜなら、spacious という単語と ocean view という必須のフレーズの両方が description フィールドに出現するためです。 その他の 2 つの文書は、ocean view しか一致していません。 同じ方法でクエリに一致したドキュメント 2 と 3 の関連性スコアが異なっていても驚くかもしれません。 これは、スコアリング式に TF/IDF だけでなく多くのコンポーネントがあるためです。 この場合、文書 3 の方が、description が短いために、少しだけ高いスコアが割り当てられています。 フィールドの長さやその他の要因が関連度スコアに与える影響については、Lucene の実際に役立つスコア付けの式に関するページを参照してください。

一部のクエリの種類 (ワイルドカード、プレフィックス、正規表現) は、常にドキュメント 全体のスコアに一定のスコアを提供します。 これにより、ランク付けに影響を与えずに、クエリ拡張によって検出された一致を結果に含めることができます。

このことが重要である理由を例で説明します。 プレフィックス検索を含むワイルドカード検索は定義上あいまいです。これは、入力が非常に多くの異なる用語で一致する可能性のある部分的な文字列であるためです。 "tour*" の入力について考えてみます。"tours"、"tourettes"、"tourmaline" に一致が見つかりました。 こうした結果の性質上、用語の相対的な重みを適切に推測することができません。 このため、スコアリングの結果としてワイルドカード、プレフィックス、正規表現の型のクエリが発生する場合、用語の頻度は無視されます。 部分的な語句と完全な語句とを含んだ複数の構成要素から成る検索要求では、予期しない一致が偏重されないよう、部分的な入力から得られた結果については、定数スコアを割り当てたうえで反映されます。

関連性のチューニング

Azure AI Search の関連度スコアは、次の 2 とおりの方法でチューニングできます。

  • スコアリング プロファイル: ランク付けされた結果リストにおいて、一連のルールに基づく重みを文書に与えます。 このページの例では、title フィールドに一致の見つかった文書の方が、description フィールドに一致の見つかった文書よりも関連性が高いと見なすことができます。 さらに、インデックスに各ホテルの価格フィールドがある場合は、価格の低いドキュメントを昇格できます。 スコアリング プロファイルを検索インデックスに追加する方法の詳細について学習します。

  • 項目ブースト (Full Lucene クエリ構文のみで使用可能): クエリ ツリーの任意の構成要素に適用できるブースト演算子 ^ が用意されています。 このページの例では、air-condition* というプレフィックスで検索する代わりに、air-condition^2||air-condition* のように単語検索にブーストを適用することもできます。そうすれば、air-condition で完全一致する語句と、プレフィックスで一致する語句との両方を検索したうえで、完全一致の語句で一致した文書の方に、より高いランクを与えることができます。 クエリでの語句ブーストの詳細について学習します。

分散されたインデックスにおけるスコア付け

Azure AI Search のすべてのインデックスは自動的に複数のシャードに分割されます。これにより、Microsoft は、サービスのスケールアップまたはスケールダウンの間に、複数のノードにインデックスをすばやく分散することができます。 検索リクエストが行われると、それは各シャードに対して個別に実行されます。 その後、各シャードから得られた結果がマージされ、スコア順に並べ替えられます (他に並べ替えが定義されていない場合)。 スコアリング関数は、シャード内のすべてのドキュメントで逆ドキュメント頻度に対して検索語頻度を重み付けするものであり、すべてのシャードで重み付けするものではないことを知ることが重要です。

つまり、まったく同じ文書でも、それらが異なるシャードに存在していれば、関連度スコアが異なる "可能性がある" ということです。 さいわい、インデックス内の文書数が増えるにつれて語句の分布が平準化され、そのような差異は総じて消失します。 特定のドキュメントがどのシャードに配置されるかを想定することはできません。 しかし文書は、そのキーが変化しなければ、常に同じシャードに割り当てられます。

一般に、順序の不変性が重要となる場合、文書を並べ替えるための特性として、文書のスコアはあまり適していません。 たとえば、まったく同じスコアの文書が 2 つあるとして、同一クエリを繰り返し実行したときに、どちらの文書が上位にランク付けされるかを毎回確実に予測することができません。 文書スコアは、検索結果群における特定の文書の相対的な関連度の高さを測る、おおよその目安としてのみ使用してください。

まとめ

商用検索エンジンが多くの人々の支持を得たことで、プライベート データに対するフルテキスト検索への期待が高まってきました。 ほぼすべての検索について言えることですが、語句の綴りが間違っていたり不完全であったりしても自分の意図がきちんと反映されるほどの利便性を、私たちは検索エンジンに求めるようになっています。 指定してもいない同義語やほぼ同等の語句に基づいた検索結果が得られることさえ、当然のように感じてしまうほどです。

技術的な観点でいえばフルテキスト検索はきわめて複雑で、洗練された言語分析と検索語の加工 (関連性の高い結果を得るために検索語を抽出、展開、変換する処理) への秩序立ったアプローチとが要求されます。 そうした本質的な複雑さもあって、検索結果はさまざまな要因によって左右されます。 フルテキスト検索のメカニズムをしっかり理解しておけば、予期しない結果に対処しようとする際に、はっきりとその効果を実感できるでしょう。

この記事では、Azure AI Search の観点からフルテキスト検索について詳しく見てきました。 ここで身に付けた知識が、検索時に遭遇しやすい問題の原因や解決策を判断するうえでの一助となればさいわいです。

次のステップ