検索結果をランク付けする

このページでは、Spanner で全文検索の検索結果をランク付けする方法について説明します。

Spanner はトピカリティ スコアの計算をサポートしており、これは高度なランキング関数を作るための基盤となります。これらのスコアは、クエリに対する結果の関連性を、クエリ内の語句の出現頻度や他のカスタマイズ可能なオプションに基づいて計算します。

次の例は、SCORE 関数を使用してランク付けされた検索を行う方法を示したものです。

GoogleSQL

SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
ORDER BY SCORE(AlbumTitle_Tokens, "fifth symphony") DESC

PostgreSQL

この例では、spanner.scorespanner.search を一緒に使用しています。

SELECT albumid
FROM albums
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')
ORDER BY spanner.score(albumtitle_tokens, 'fifth symphony') DESC

SCORE 関数を使用してクエリ語句をスコアリングする

SCORE 関数は、クエリ内の各語句のスコアを計算し、そのスコアを結合します。語句ごとのスコアは、おおまかに用語頻度 - 逆文書頻度(TF/IDF)に基づいています。スコアは、レコードの最終的な並び順を決める要素の一つです。クエリは、スコアを他のシグナルと組み合わせて使用します。たとえば、鮮度がトピカリティ スコアに影響を与えるといった具合です。

現在の実装では、TF/IDF の IDF 部分は enhance_query=>true が使用されている場合にのみ利用可能です。特定の検索インデックスではなく、Google 検索で使用されるウェブコーパス全体に基づいて、単語の相対頻度が計算されます。rquery 拡張機能が有効になっていない場合、スコアリングでは語句頻度(TF)コンポーネントのみが使用されます(つまり、IDF 語句は 1 に設定されます)。

SCORE 関数は、Spanner が並び順を決めるために使用する関連性スコアとなる値を返します。これらの値は単独では機能しません。スコアが高いほど、クエリとの一致度が高くなります。

通常、queryenhance_query などの引数は、SEARCH 関数と SCORE 関数の両方で同じ値にすることで、情報の取得とランキングの整合性を保ちます。

これを行うために、文字列リテラルではなくクエリ パラメータを指定した引数を使用し、SEARCH 関数と SCORE 関数で同じクエリ パラメータを指定することをおすすめします。

複数の列をスコアリングする

Spanner は SCORE 関数を使用して、各フィールドを個別にスコアリングします。それから、クエリによってこれらの個々のスコアが結合されます。これを行う一般的な方法は、個別のスコアを合計し、その後、ユーザーが指定したフィールドの重み付けに応じてスコアを調整することです(重みは SQL クエリのパラメータとして指定されます)。

たとえば、次のクエリは 2 つの SCORE 関数の出力を結合します。

GoogleSQL

SELECT AlbumId
FROM Albums
WHERE SEARCH(Title_Tokens, @p1) AND SEARCH(Studio_Tokens, @p2)
ORDER BY SCORE(Title_Tokens, @p1) * @titleweight + SCORE(Studio_Tokens, @p2) * @studioweight
LIMIT 25

PostgreSQL

この例では、クエリ パラメータ $1$2 を使っており、それぞれ「fifth symphony」と「blue note」にバインドされています。

SELECT albumid
FROM albums
WHERE spanner.search(title_tokens, $1) AND spanner.search(studio_tokens, $2)
ORDER BY spanner.score(title_tokens, $1) * $titleweight
        + spanner.score(studio_tokens, $2) * $studioweight
LIMIT 25

次の例では、2 つのブースト パラメータを追加します。

  • 鮮度(FreshnessBoost)は (1 + @freshnessweight * GREATEST(0, 30 - DaysOld) / 30) でスコアを増やします。
  • 人気度(PopularityBoost)は、係数 (1 + IF(HasGrammy, @grammyweight, 0) を掛けることでスコアを増やします。

わかりやすくするために、このクエリでは WITH 演算子を使用しています。

GoogleSQL

SELECT AlbumId
FROM Albums
WHERE SEARCH(Title_Tokens, @p1) AND SEARCH(Studio_Tokens, @p2)
ORDER BY WITH(
  TitleScore AS SCORE(Title_Tokens, @p1) * @titleweight,
  StudioScore AS SCORE(Studio_Tokens, @p2) * @studioweight,
  DaysOld AS (UNIX_MICROS(CURRENT_TIMESTAMP()) - ReleaseTimestamp) / 8.64e+10,
  FreshnessBoost AS (1 + @freshnessweight * GREATEST(0, 30 - DaysOld) / 30),
  PopularityBoost AS (1 + IF(HasGrammy, @grammyweight, 0)),
  (TitleScore + StudioScore) * FreshnessBoost * PopularityBoost)
LIMIT 25

PostgreSQL

この例では、クエリ パラメータ $1$2$3$4$5$6 を使用しています。これらのパラメータは、それぞれ titlequerystudioquerytitleweightstudioweightgrammyweightfreshnessweight に指定された値にバインドされています。

SELECT albumid
FROM
  (
    SELECT
      albumid,
      spanner.score(title_tokens, $1) * $3 AS titlescore,
      spanner.score(studio_tokens, $2) * $4 AS studioscore,
      (extract(epoch FROM current_timestamp) * 10e+6 - releasetimestamp) / 8.64e+10 AS daysold,
      (1 + CASE WHEN hasgrammy THEN $5 ELSE 0 END) AS popularityboost
    FROM albums
    WHERE spanner.search(title_tokens, $1) AND spanner.search(studio_tokens, $2)
  ) AS subquery
ORDER BY (subquery.TitleScore + subquery.studioscore)
  * (1 + $6 * greatest(0, 30 - subquery.daysold) / 30) * subquery.popularityboost
LIMIT 25

必要に応じて、検索とスコアリングの両方で TOKENLIST_CONCAT を使用して、クエリを簡素化することもできます。

GoogleSQL

SELECT AlbumId
FROM Albums
WHERE SEARCH(TOKENLIST_CONCAT([Title_Tokens, Studio_Tokens]), @p)
ORDER BY SCORE(TOKENLIST_CONCAT([Title_Tokens, Studio_Tokens]), @p)
LIMIT 25

PostgreSQL

この例では spanner.tokenlist_concat を使用しています。 クエリ パラメータ $1 は「blue note」にバインドされています。

SELECT albumid
FROM albums
WHERE spanner.search(spanner.tokenlist_concat(ARRAY[title_tokens, studio_tokens]), $1)
ORDER BY spanner.score(spanner.tokenlist_concat(ARRAY[title_tokens, studio_tokens]), $1)
LIMIT 25

クエリ順序の一致をブーストする

Spanner は、クエリ語句がクエリの並び順どおりに一致している値に対して、SCORE 関数の出力に乗算によるブーストを適用します。このブーストには、部分一致と完全一致の 2 つの種類があります。部分一致ブーストは、次の場合に適用されます。

  1. TOKENLIST に、クエリの元の語句がすべて含まれている。
  2. トークン同士が隣接しており、クエリでの並び順どおりである。

連結詞、否定、フレーズに対しては、特別なルールがあります。

  • 否定を含むクエリは、部分一致ブーストを受けられません。
  • 結合詞を含むクエリは、その結合詞の一部が適切な場所に現れるとブーストの対象となります。
  • フレーズを含むクエリは、そのフレーズが TOKENLIST に現れ、かつクエリ内のフレーズの左側の語句が TOKENLIST 内でもフレーズの左側に現れる場合にブーストを受けます。右側の語句に対しても同様です。

上記のすべてのルールが満たされ、かつクエリの最初と最後のトークンがドキュメントの最初と最後のトークンと一致するときに、完全一致のブーストを適用します。

ドキュメントの例: Bridge Over Troubled Water

クエリ 適用されるブースト
Bridge Troubled ブーストなし
Bridge Over - other water ブーストなし
Bridge (Over OR Troubled) Water ブーストなし
Bridge Over 部分的なブースト
Bridge Over(Troubled または Water) 部分的なブースト
Bridge Over Troubled Water 完全一致ブースト
Bridge「Over Troubled」Water 完全一致ブースト
Bridge ("Over Troubled" OR missingterm) Water 完全一致ブースト

取得深度を制限する

多くの場合、検索インデックスには何百万ものドキュメントが含まれています。述語の選択性が低いクエリの場合、すべての結果をランク付けするのは現実的ではありません。クエリのスコアリングには通常、次の 2 つの上限があります。

  1. 取得深度の上限: スコアリングする行の最大数。
  2. 結果セットのサイズの上限: クエリが返す行の最大数(通常はページサイズ)。

クエリでは、SQL サブクエリを使用して取得深度を制限できます。

GoogleSQL

SELECT *
FROM (
  SELECT AlbumId, Title_Tokens
  FROM Albums
  WHERE SEARCH(Title_Tokens, @p1)
  ORDER BY ReleaseTimestamp DESC
  LIMIT @retrieval_limit
)
ORDER BY SCORE(Title_Tokens, @p1)
LIMIT @page_size

PostgreSQL

この例では、title_queryretrieval_limitpage_size に指定された値にそれぞれバインドされるクエリ パラメータ $1$2$3 を使用しています。

SELECT *
FROM (
  SELECT albumid, title_tokens
  FROM albums
  WHERE spanner.search(title_tokens, $1)
  ORDER BY releasetimestamp DESC
  LIMIT $2
) AS subquery
ORDER BY spanner.score(subquery.title_tokens, $1)
LIMIT $3

これは、Spanner が最も重要なランキング シグナルを使ってインデックスを並べ替えている場合に特に効果的です。

次のステップ