Decay Ranker の概要
従来のベクトル検索では、結果は純粋にベクトル類似度(数学空間におけるベクトルの近さ)に基づいてランキングされます。しかし実際のアプリケーションでは、コンテンツの真の関連性は意味的な類似性だけでなく、より多くの要素に依存することが少なくありません。
次のような日常的なシナリオを考えてみましょう。
-
ニュース検索で、昨日の記事が3年前の類似記事よりも上位に表示されるべき場合
-
レストラン検索で、車で30分かかる店舗よりも徒歩5分の店舗を優先したい場合
-
ECサイトで、検索クエリとの類似度がやや低くてもトレンド商品を上位に表示したい場合
これらのシナリオには共通のニーズがあります。それは、ベクトル類似度と、時間、距離、人気度といった数値的要因のバランスを取ることです。
Zilliz Cloud の Decay Ranker は、数値フィールドの値に基づいて検索ランキングを調整することで、このニーズに応えます。ベクトル類似度とデータの「新しさ」や「近さ」といった数値的特性のバランスを取り、より直感的で文脈に即した検索体験を実現します。
使用上の注意
-
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 距離メトリック(値が小さいほど類似度が高い)の場合:
plaintextnormalized_score = 1.0 - (2 × arctan(score))/πこれにより距離が0〜1の類似度スコアに変換され、値が大きいほど類似度が高いことを示します。
-
IP、COSINE、BM25 メトリック(スコアが高いほど一致度が高い)の場合: スコアは正規化せずにそのまま使用されます。
ステージ2: Decay スコアの計算
次に、Zilliz Cloud が選択された Decay Ranker を用いて、数値フィールドの値(タイムスタンプや距離など)に基づき Decay スコアを計算します。
-
各 Decay Ranker は生の数値を0〜1の正規化された関連性スコアに変換します
-
Decay スコアは、理想点からの「距離」に基づいてアイテムの関連性を表します
具体的な計算式は Decay Ranker の種類によって異なります。Decay スコアの計算方法の詳細については、Gaussian Decay、Exponential Decay、Linear Decay の専用ページを参照してください。
ステージ3: 最終スコアの算出
最後に、Zilliz Cloud が正規化類似度スコアと Decay スコアを組み合わせて、最終的なランキングスコアを算出します。
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 は Decay 係数を適用する前のベース類似度スコアとして0.91を使用します。
Decay Ranking の実例
時間ベースの Decay を使用して 「AI research papers」 を検索する実践的なシナリオで、Decay Ranking の動作を確認してみましょう。
この例では、Decay スコアが時間の経過に伴う関連性の低下を反映しています。新しい論文ほど1.0に近いスコアとなり、古い論文ほど低いスコアになります。これらの値は特定の Decay Ranker を使用して計算されます。詳細については、適切な Decay Ranker の選択 を参照してください。
| 論文 | ベクトル類似度 | 正規化類似度スコア | 公開日 | Decay スコア | 最終スコア | 最終順位 |
|---|---|---|---|---|---|---|
| 論文A | 高 | 0.85 (COSINE) | 2週間前 | 0.80 | 0.68 | #2 |
| 論文B | 非常に高 | 0.92 (COSINE) | 6か月前 | 0.45 | 0.41 | #3 |
| 論文C | 中 | 0.75 (COSINE) | 1日前 | 0.98 | 0.74 | #1 |
| 論文D | 中〜高 | 0.76 (COSINE) | 3週間前 | 0.70 | 0.53 | #4 |
Decay リランキングがない場合、論文Bは純粋なベクトル類似度(0.92)に基づいて最上位にランクされます。しかし、Decay リランキングを適用すると以下のようになります。
-
論文Cは類似度が中程度にもかかわらず、非常に新しい(昨日公開)ため1位に浮上します
-
論文Bは類似度が非常に高いものの、比較的古いため3位に後退します
-
論文DはL2距離(値が小さいほど良い)を使用しているため、Decay を適用する前にスコアが1.2から0.76に正規化されます
適切な Decay Ranker の選択
Zilliz Cloud は、それぞれ特定のユースケース向けに設計された gauss、exp、linear という異なる Decay Ranker を提供します。
Decay Ranker | 特徴 | 推奨ユースケース | シナリオ例 |
|---|---|---|---|
Gaussian ( | 適度に広がりを持つ、自然で緩やかな減衰 |
| レストラン検索で、3km先の高評価な店舗も、近隣の選択肢より順位は下がるものの引き続き表示される |
Exponential ( | 初期は急激に減少するが、ロングテールを維持する |
| ニュースアプリで、昨日の記事は1週間前のコンテンツよりはるかに上位になるが、関連性の高い古い記事も引き続き表示される |
Linear ( | 明確なカットオフを持つ、一定で予測可能な減衰 |
| イベント検索で、2週間先のウィンドウを超えるイベントは一切表示されない |
各 Decay Ranker のスコア計算方法や具体的な減衰パターンの詳細については、専用ドキュメントを参照してください。
実装例
Decay ranker は、Zilliz Cloud における標準ベクトル検索およびハイブリッド検索の両方に適用できます。以下に、この機能を実装するための主要なコードスニペットを示します。
decay 関数を使用する前に、まず decay 計算に用いる適切な数値フィールド(タイムスタンプや距離など)を持つコレクションを作成する必要があります。コレクションのセットアップ、スキーマ定義、データ挿入を含む完全な動作例については、「チュートリアル: Milvus で時間ベースのランキングを実装する」を参照してください。
Decay ranker の作成
decay ランキングを実装するには、まず適切な設定で Function オブジェクトを定義します。
- Python
- Java
- NodeJS
- Go
- cURL
- C++
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
auto rerank = std::make_shared<milvus::DecayRerank>("time_decay");
rerank->AddInputFieldName("timestamp");
rerank->SetFunction("gauss");
rerank->SetOrigin(1735689600);
rerank->SetScale(7 * 24 * 60 * 60);
rerank->SetOffset(24 * 60 * 60);
rerank->SetDecay(0.5);
パラメーター | 必須 | 説明 | 値/Example |
|---|---|---|---|
| はい | 検索実行時に使用される関数の識別子です。ユースケースに即した分かりやすい名前を指定してください。 |
|
| はい | decay スコアの計算に使用する数値フィールドです。decay 計算の対象となるデータ属性を指定します(例: 時間ベースの decay の場合はタイムスタンプ、位置ベースの decay の場合は座標)。 コレクション内の関連する数値を含むフィールドである必要があります。INT8/16/32/64, FLOAT、DOUBLE をサポートしています。 |
|
| はい | 作成する関数の種類を指定します。 すべての decay ranker において |
|
| はい | 使用するリランキング手法を指定します。 decay ランキング機能を有効にするには、 |
|
| はい | 適用する数学的な decay ranker を指定します。これにより、関連性が低下する際の曲線の形状が決まります。 適切な関数の選択については、「適切な decay ranker の選択」セクションを参照してください。 |
|
| はい | decay スコア算出の基準となる参照点です。この値と一致する項目が最大の関連性スコアを得ます。 時間ベースの decay の場合、時間の単位はコレクションのデータと一致させる必要があります。 |
|
| はい | 関連性が 時間ベースの decay の場合、時間の単位はコレクションのデータと一致させる必要があります。 値を大きくすると関連性の低下が緩やかになり、小さくすると急激に低下します。 |
|
| いいえ |
時間ベースの decay の場合、時間の単位はコレクションのデータと一致させる必要があります。
|
|
| いいえ |
0 から 1 の間で指定する必要があります。 |
|
標準ベクトル検索への適用
decay ranker を定義したら、検索実行時に ranker パラメーターへ渡すことで適用できます。
- Python
- Java
- NodeJS
- Go
- cURL
- C++
# 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
auto function_score = std::make_shared<milvus::FunctionScore>();
function_score->AddFunction(rerank);
auto request = milvus::SearchRequest()
.WithCollectionName(collection_name)
.WithAnnsField("dense")
.WithRerank(function_score)
.AddOutputField("document")
.AddOutputField("timestamp")
.AddFloatVector(your_query_vector);
milvus::SearchResponse response;
auto status = client->Search(request, response);
if (!status.IsOk()) {
std::cout << status.Message() << std::endl;
}