Decay Ranker の概要
従来のベクトル検索では、結果は純粋にベクトル類似度(数学的空間におけるベクトルの近さ)に基づいてランキングされます。しかし実際のアプリケーションでは、コンテンツの真の関連性は意味的類似度だけではなく、他の要素にも依存することがよくあります。
以下のような日常的なシナリオを考えてみましょう。
- 昨日のニュース記事が、3年前の類似記事よりも上位に表示されるニュース検索
- 30分のドライブが必要な店舗よりも、徒歩5分の飲食店を優先するレストラン検索
- 検索クエリとの類似度がやや低くても、トレンド中の商品を優先表示するECプラットフォーム
これらのシナリオには共通点があります。それは、ベクトル類似度と時間・距離・人気度などの数値的要素をバランスよく考慮する必要があることです。
Zilliz Cloud の Decay ranker は、このニーズに対応するために、数値フィールドの値に基づいて検索結果のランキングを調整します。「新鮮さ(freshness)」や「近接性(nearness)」など、データに含まれる数値的特性とベクトル類似度をバランスさせることで、より直感的で文脈に即した検索体験を実現します。
使用上の注意
-
Decay ranking はグループ化検索とは併用できません。
-
Decay ranking に使用するフィールドは数値型(
INT8、INT16、INT32、INT64、FLOAT、またはDOUBLE)である必要があります。 -
各 Decay ranker は1つの数値フィールドのみを使用できます。
-
時間単位の一貫性:時間ベースの decay ranking を使用する場合、
origin、scale、およびoffsetパラメータの単位は、コレクション内のデータで使用されている単位と一致させる必要があります。-
コレクションがタイムスタンプを秒単位で保存している場合、すべてのパラメータも秒単位で指定します
-
コレクションがタイムスタンプをミリ秒単位で保存している場合、すべてのパラメータもミリ秒単位で指定します
-
コレクションがタイムスタンプをマイクロ秒単位で保存している場合、すべてのパラメータもマイクロ秒単位で指定します
-
動作の仕組み
Decay ranking は、時間や地理的距離といった数値的要素をランキング処理に組み込むことで、従来のベクトル検索を強化します。このプロセスは以下のステージで構成されています。
ステージ 1: 正規化された類似度スコアの計算
まず、Zilliz Cloud はベクトル類似度スコアを計算し、一貫した比較が行えるように正規化します。
-
L2 および JACCARD 距離メトリクスの場合(値が小さいほど類似度が高い):
normalized_score = 1.0 - (2 × arctan(score))/πこれは、距離を0〜1の類似度スコアに変換し、数値が大きいほど良い結果を示します。
-
IP、COSINE、およびBM25メトリクスの場合(これらのメトリクスでは、すでに高いスコアがより良いマッチを示します):スコアは正規化せずにそのまま使用されます。
ステージ 2: 減衰スコアの計算
次に、Zilliz Cloud は選択した減衰ランカーを使用して、数値フィールドの値(タイムスタンプや距離など)に基づく減衰スコアを計算します。
-
各減衰ランカーは、生の数値を0〜1の正規化された関連性スコアに変換します
-
減衰スコアは、「理想的なポイント」からの「距離」に基づいてアイテムの関連性を表します
具体的な計算式は、減衰ランカーのタイプによって異なります。減衰スコアの計算方法の詳細については、それぞれの専用ページをご参照ください:ガウス減衰、指数減衰、線形減衰。
ステージ 3: 最終スコアの算出
最後に、Zilliz Cloud は正規化された類似度スコアと減衰スコアを組み合わせて、最終的なランキングスコアを生成します。
final_score = normalized_similarity_score × decay_score
ハイブリッド検索(複数のベクトルフィールドを組み合わせる)の場合、Zilliz Cloud は検索リクエストの中で最大の正規化済み類似度スコアを採用します。
final_score = max([normalized_score₁, normalized_score₂, ..., normalized_scoreₙ]) × decay_score
たとえば、ハイブリッド検索において、ある研究論文がベクトル類似度で0.82、BM25ベースのテキスト検索で0.91をスコアした場合、Zilliz Cloudは減衰係数を適用する前に0.91を基本類似度スコアとして使用します。
実際の減衰ランキング
実用的なシナリオで減衰ランキングを見てみましょう。ここでは「AI研究論文」を時間に基づく減衰付きで検索します:
この例では、減衰スコアは時間の経過とともに関連性が低下することを反映しています。新しい論文ほど1.0に近いスコアを受け取り、古い論文ほど低いスコアになります。これらの値は特定の減衰ランカーを使用して計算されています。詳細については、適切な減衰ランカーの選択をご参照ください。
論文 | ベクトル類似度 | 正規化済み類似度スコア | 公開日 | 減衰スコア | 最終スコア | 最終順位 |
|---|---|---|---|---|---|---|
論文 A | 高 | 0.85 ( | 2週間前 | 0.80 | 0.68 | 2 |
論文 B | 非常に高 | 0.92 ( | 6か月前 | 0.45 | 0.41 | 3 |
論文 C | 中 | 0.75 ( | 1日前 | 0.98 | 0.74 | 1 |
論文 D | 中〜高 | 0.76 ( | 3週間前 | 0.70 | 0.53 | 4 |
減衰による再ランキングを行わなければ、論文Bは純粋なベクトル類似度(0.92)により最上位になります。しかし、減衰再ランキングを適用すると:
-
論文Cは類似度が「中」であるにもかかわらず、非常に新しい(昨日公開)ため1位にジャンプアップします
-
論文Bは優れた類似度を持つものの比較的古いため、3位にまで下がります
-
論文DはL2距離(小さいほど良い)を使用しているため、1.2から0.76に正規化された後に減衰が適用されています
適切な減衰ランカーの選択
Zilliz Cloudは、それぞれ異なるユースケース向けに設計されたgauss、exp、linearの3種類の減衰ランカーを提供しています:
減衰ランカー | 特徴 | 理想的なユースケース | 例となるシナリオ |
|---|---|---|---|
ガウス ( | 自然で緩やかな減少傾向を持ち、中程度まで影響が及ぶ |
| レストラン検索では、3 km離れた高品質な店舗も表示されますが、近くの選択肢よりは順位が下がります |
指数 ( | 最初に急激に減少し、その後も長い尾を引く |
| ニュースアプリでは、昨日の記事が1週間前のコンテンツよりもはるかに高い順位となりますが、関連性が非常に高い古い記事も表示されます |
線形 ( | 一貫性があり予測可能な減少傾向を持ち、明確なカットオフがある |
| イベント検索では、2週間以上先のイベントはまったく表示されません |
各減衰ランカーがスコアをどのように計算し、どのような減少パターンを持つのかについての詳細は、以下の専用ドキュメントをご参照ください:
実装例
減衰ランカーは、Zilliz Cloudにおける標準的なベクトル検索およびハイブリッド検索の両方に適用できます。以下にこの機能を実装するための主要なコードスニペットを示します。
減衰関数を使用する前に、まず減衰計算に使用するタイムスタンプや距離などの数値フィールドを含むコレクションを作成しておく必要があります。コレクションのセットアップ、スキーマ定義、データ挿入を含む完全な動作例については、チュートリアル: Milvusで時間ベースのランキングを実装するをご参照ください。
減衰ランカーの作成
減衰ランキングを実装するには、まず適切な設定でFunctionオブジェクトを定義します:
- Python
- Java
- NodeJS
- Go
- cURL
from pymilvus import Function, FunctionType
# Create a decay function for timestamp-based decay
# Note: All time parameters must use the same unit as your collection data
rerank = Function(
name="time_decay", # Function identifier
input_field_names=["timestamp"], # Numeric field to use for decay
function_type=FunctionType.RERANK, # Must be set to RERANK for decay rankers
params={
"reranker": "decay", # Specify decay reranker. Must be "decay"
"function": "gauss", # Choose decay function type: "gauss", "exp", or "linear"
"origin": int(datetime.datetime(2025, 1, 15).timestamp()), # Reference point (seconds)
"scale": 7 * 24 * 60 * 60, # 7 days in seconds (must match collection data unit)
"offset": 24 * 60 * 60, # 1 day no-decay zone (must match collection data unit)
"decay": 0.5 # Half score at scale distance
}
)
import io.milvus.v2.service.vector.request.ranker.DecayRanker;
import java.time.ZoneId;
import java.time.ZonedDateTime;
ZonedDateTime zdt = ZonedDateTime.of(2025, 1, 25, 0, 0, 0, 0, ZoneId.systemDefault());
DecayRanker rerank = DecayRanker.builder()
.name("time_decay")
.inputFieldNames(Collections.singletonList("timestamp"))
.function("gauss")
.origin(zdt.toInstant().toEpochMilli())
.scale(7 * 24 * 60 * 60)
.offset(24 * 60 * 60)
.decay(0.5)
.build();
import {FunctionType } from "@zilliz/milvus2-sdk-node";
const rerank = {
name: "time_decay",
input_field_names: ["timestamp"],
function_type: FunctionType.RERANK,
params: {
reranker: "decay",
function: "gauss",
origin: new Date(2025, 1, 15).getTime(),
scale: 7 * 24 * 60 * 60,
offset: 24 * 60 * 60,
decay: 0.5,
},
};
// go
# restful
パラメータ | 必須? | 説明 | 値/例 |
|---|---|---|---|
| はい | 検索実行時に使用される関数の識別子です。ユースケースに関連したわかりやすい名前を指定してください。 |
|
| はい | 減衰スコア計算に使用する数値フィールドです。減衰計算に使用するデータ属性(例:時間ベースの減衰にはタイムスタンプ、位置ベースの減衰には座標)を決定します。 コレクション内に存在し、関連する数値を含むフィールドである必要があります。INT8/16/32/64、FLOAT、DOUBLE をサポートしています。 |
|
| はい | 作成する関数のタイプを指定します。 すべての減衰ランカーに対して |
|
| はい | 使用するリランキング手法を指定します。 減衰ランキング機能を有効にするには、 |
|
| はい | 適用する数学的減衰ランカーを指定します。関連性の低下カーブの形状を決定します。 適切な関数の選択については、適切な減衰ランカーの選択セクションをご参照ください。 |
|
| はい | 減衰スコアの計算基準となる参照点です。この値を持つアイテムは最大の関連性スコアを受け取ります。 時間ベースの減衰の場合、時間単位はコレクションのデータと一致している必要があります。 |
|
| はい |
時間ベースの減衰の場合、時間単位はコレクションのデータと一致している必要があります。 大きな値は関連性の緩やかな低下を、小さな値は急激な低下をもたらします。 |
|
| いいえ |
時間ベースの減衰の場合、時間単位はコレクションのデータと一致している必要があります。
|
|
| いいえ |
0 から 1 の間である必要があります。 |
|
標準的なベクトル検索への適用
減衰ランカーを定義した後、検索操作時に ranker パラメータに渡すことで適用できます:
- Python
- Java
- NodeJS
- Go
- cURL
# Use the decay function in standard vector search
results = milvus_client.search(
collection_name,
data=[your_query_vector], # Replace with your query vector
anns_field="vector_field",
limit=10,
output_fields=["document", "timestamp"], # Include the decay field in outputs to see values
ranker=rerank, # Apply the decay ranker here
consistency_level="Strong"
)
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.response.SearchResp;
import io.milvus.v2.service.vector.request.data.EmbeddedText;
SearchReq searchReq = SearchReq.builder()
.collectionName(COLLECTION_NAME)
.data(Collections.singletonList(new EmbeddedText("search query")))
.annsField("vector_field")
.limit(10)
.outputFields(Arrays.asList("document", "timestamp"))
.functionScore(FunctionScore.builder()
.addFunction(rerank)
.build())
.build();
SearchResp searchResp = client.search(searchReq);
const result = await milvusClient.search({
collection_name: collection_name,
data: [your_query_vector], // Replace with your query vector
anns_field: "dense",
limit: 10,
output_fields: ["document", "timestamp"],
rerank: rerank,
consistency_level: "Strong",
});
// go
# restful