指数衰减
指数衰减会使搜索结果呈现出初始阶段急剧下降,随后是长尾的特征。就像突发新闻周期一样,相关性起初迅速减弱,但有些报道随着时间的推移仍保持重要性,指数衰减对刚刚超出理想范围的项目大举罚分,同时仍让距离较远的项目仍可以被发现。当你希望高度优先考虑接近程度或时效性,但又不想完全排除距离较远的选项时,这种方法是理想的选择。
与其他衰减函数不同:
-
高斯衰减产生了更渐进的钟形衰减
-
线性衰减以恒定速率减小,直到精确地达到零
指数衰减独特地“前置”罚分,在衰减早期就开始大举罚分,但仍保持最小但非零的相关性得分长尾。
何时使用指数衰减
指数衰减对以下情况特别有效:
| 用例 | 示例 | 为什么指数衰减运作良好 |
|---|---|---|
| 新闻提要 | 突发新闻门户网站 | 在仍展示几天前重要报道的同时,迅速降低旧新闻的相关性 |
| 社交媒体时间线 | 动态更新、状态更新 | 强调新鲜内容,但允许热门的旧内容浮出水面 |
| 通知系统 | 告警优先级排序 | 在保持重要警报可见性的同时,为近期警报营造紧迫感 |
| 限时抢购 | 限时优惠 | 随着截止日期临近,能见度迅速降低 |
在以下情况下选择指数衰减:
-
用户期望非常近期或附近的项目在结果中占据主导地位
-
如果较旧或较久远的项目具有极高的相关性,那么它们仍应是可被发现的
-
相关性衰减应前置(开始时更陡峭,随后更平缓)
急剧下降原则
指数衰减形成的曲线起初下降迅速,随后逐渐趋于平缓,形成一条趋近但永远不会达到零的长尾。这种数学模式在自然现象中频繁出现,如放射性衰变、人口减少以及信息相关性随时间的变化。
所有时间参数(origin、offset、scale)必须使用与 Collection 中数据相同的单位。如果您的 Collection 以不同的单位(毫秒、微秒)存储时间戳,请相应地调整所有参数。

上图展示了指数衰减如何影响数字新闻平台中新闻文章的排名:
-
origin(当前时间):又被称为原点,表示当前时刻,相关性达到最大值(1.0)。 -
offset(3小时):又被称为偏移量,表示“突发新闻窗口”——所有在过去3小时内发布的报道都保持完整的相关性得分(1.0),确保非常近期的新闻不会因微小的时间差异而被不必要地扣分。 -
decay(0.5):又被称衰减值,表示在关注范围边缘处的得分——此参数控制得分随时间衰减的剧烈程度。 -
scale(24小时):又称为关注范围,表示相关性降至衰减值的时间段——恰好24小时前的新闻文章,其相关性得分减半(0.5)。
从曲线中可以看出,超过 24 小时的新闻文章的相关性持续下降,但永远不会完全降至零。即使是几天前的报道也保留了一定的最低相关性,这使得重要但较旧的新闻仍能出现在你的信息流中(尽管排名较低)。
这种行为模仿了新闻相关性通常的运作方式——非常新的报道占据主导地位,但如果较旧的重要报道与用户的兴趣特别相关,它们仍然可以脱颖而出。
公式
计算指数衰减得分的数学公式为:
其中:
用通俗易懂的语言来解释一下:
-
计算字段值离原点的距离:。
-
减去偏移量(如果有的话),但结果永远不能小于零:。
-
乘以,该值由您的缩放和衰减参数计算得出。
-
取指数,它会给出一个介于0和1之间的值:。
该计算将您的缩放和衰减参数转换为指数函数的速率参数。更负的会导致更陡峭的初始下降。
使用指数衰减
指数衰减可应用于 Zilliz Cloud 中的标准向量搜索和混合搜索操作。以下是实现此功能的关键代码片段。
在使用 Decay Ranker 之前,你必须首先创建一个包含适当数字类型字段(如时间戳、距离等)的集合,这些字段将用于衰减计算。有关包括 Collection 设置、 Schema 定义和数据插入的完整工作示例,请参考教程:实现基于时间的搜索结果重排。
创建一个 Decay Ranker
在您的 Collection 中设置了一个数字字段(在本示例中为publish_time)之后,创建一个指数衰减 Decay Ranker:
时间单位一致性:使用基于时间的衰减时,确保 origin、scale 和 offset 参数使用与您的 Collection 中的数据使用相同的时间单位。如果您的 Collection 中的数据以秒为单位存储时间戳,则所有参数都使用秒。如果使用毫秒,则所有参数都使用毫秒。
- Python
- Java
- Go
- Rust
- C++
- NodeJS
- cURL
from pymilvus import Function, FunctionType
import datetime
# Create an exponential decay ranker for news recency
# Note: All time parameters must use the same unit as your collection data
rerank = Function(
name="news_recency", # Function identifier
input_field_names=["publish_time"], # Numeric field to use
function_type=FunctionType.RERANK, # Function type. Must be RERANK
params={
"reranker": "decay", # Specify decay reranker
"function": "exp", # Choose exponential decay
"origin": int(datetime.datetime.now().timestamp()), # Current time (seconds, matching collection data)
"offset": 3 * 60 * 60, # 3 hour breaking news window (seconds)
"decay": 0.5, # Half score at scale distance
"scale": 24 * 60 * 60 # 24 hours (in seconds, matching collection data)
}
)
import java.util.Collections;
import io.milvus.v2.service.vector.request.ranker.DecayRanker;
DecayRanker rerank = DecayRanker.builder()
.name("news_recency")
.inputFieldNames(Collections.singletonList("publish_time"))
.function("exp")
.origin(System.currentTimeMillis() / 1000) // Current time (seconds, matching collection data)
.offset(3 * 60 * 60) // 3 hour breaking news window (seconds)
.decay(0.5) // Half score at scale distance
.scale(24 * 60 * 60) // 24 hours (in seconds, matching collection data)
.build();
import (
"time"
"github.com/milvus-io/milvus/client/v3/entity"
)
// Create an exponential decay ranker for news recency
// Note: All time parameters must use the same unit as your collection data
rerank := entity.NewFunction().
WithName("news_recency").
WithInputFields("publish_time").
WithType(entity.FunctionTypeRerank).
WithParam("reranker", "decay").
WithParam("function", "exp").
WithParam("origin", time.Now().Unix()).
WithParam("offset", 3*60*60).
WithParam("decay", 0.5).
WithParam("scale", 24*60*60)
use milvus::v2::prelude::*;
// Create an exponential decay ranker for news recency
// Note: All time parameters must use the same unit as your collection data
let origin = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let rerank = {
let mut rerank = DecayRerank::new()
.name("news_recency")
.decay_function("exp")
.origin(origin)
.offset(3 * 60 * 60)
.decay(0.5)
.scale(24 * 60 * 60);
rerank.function(rerank.get_function().clone().input_fields(["publish_time"]))
};
#include <ctime>
#include "milvus/MilvusClientV2.h"
auto rerank = std::make_shared<milvus::DecayRerank>("news_recency");
rerank->AddInputFieldName("publish_time");
rerank->SetFunction("exp");
rerank->SetOrigin(std::time(nullptr)); // Current time (seconds, matching collection data)
rerank->SetScale(24 * 60 * 60);
rerank->SetOffset(3 * 60 * 60);
rerank->SetDecay(0.5);
import { FunctionType } from "@zilliz/milvus2-sdk-node";
const rerank = {
name: "news_recency",
input_field_names: ["publish_time"],
type: FunctionType.RERANK,
params: {
reranker: "decay",
function: "exp",
origin: Math.floor(Date.now() / 1000), // Current time (seconds)
offset: 3 * 60 * 60, // 3 hour breaking news window (seconds)
decay: 0.5, // Half score at scale distance
scale: 24 * 60 * 60, // 24 hours (seconds)
},
};
# Note: The RESTful API does not expose a standalone "create a decay ranker"
# endpoint. The decay function is passed inline through the functionScore
# field of the search request (see the next section).
在标准向量搜索中使用
定义 Decay Ranker 后,您可以在搜索请求中通过将其传递给 ranker 参数来应用它:
- Python
- Java
- Go
- Rust
- C++
- NodeJS
- cURL
# Apply decay ranker to vector search
result = milvus_client.search(
collection_name,
data=[your_query_vector], # Replace with your query vector
anns_field="dense", # Vector field to search
limit=10, # Number of results
output_fields=["title", "publish_time"], # Fields to return
ranker=rerank, # Apply the decay ranker
consistency_level="Strong"
)
import java.util.Arrays;
import java.util.Collections;
import io.milvus.v2.common.ConsistencyLevel;
import io.milvus.v2.service.vector.request.FunctionScore;
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.response.SearchResp;
String COLLECTION_NAME = "collection_name";
SearchReq searchReq = SearchReq.builder()
.collectionName(COLLECTION_NAME)
.data(Collections.singletonList(new FloatVec(Arrays.asList(0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f)))) // Replace with your query vector
.annsField("dense") // Vector field to search
.limit(10) // Number of results
.outputFields(Arrays.asList("title", "publish_time")) // Fields to return
.functionScore(FunctionScore.builder()
.addFunction(rerank) // Apply the decay ranker
.build())
.consistencyLevel(ConsistencyLevel.STRONG)
.build();
SearchResp searchResp = client.search(searchReq);
import (
"context"
"github.com/milvus-io/milvus/client/v3/entity"
"github.com/milvus-io/milvus/client/v3/milvusclient"
)
// Apply decay ranker to vector search
resultSets, err := cli.Search(ctx, milvusclient.NewSearchOption(
"collection_name", 10, []entity.Vector{
entity.FloatVector{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8}, // Replace with your query vector
}).
WithANNSField("dense"). // Vector field to search
WithOutputFields("title", "publish_time"). // Fields to return
WithFunctionReranker(rerank). // Apply the decay ranker
WithConsistencyLevel(entity.ClStrong))
if err != nil {
panic(err)
}
use milvus::v2::prelude::*;
// Apply decay ranker to vector search
let response = client.search(
SearchRequest::builder()
.collection_name("collection_name")
.vector_field("dense") // Vector field to search
.vectors(SearchVectors::Float(vec![vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]])) // Replace with your query vector
.limit(10) // Number of results
.output_fields(["title", "publish_time"]) // Fields to return
.rerank(FunctionScore::new().add_function(rerank)) // Apply the decay ranker
.consistency_level(ConsistencyLevel::Strong)
.build()?,
).await?;
#include <iostream>
#include "milvus/MilvusClientV2.h"
auto function_score = std::make_shared<milvus::FunctionScore>();
function_score->AddFunction(rerank);
auto request = milvus::SearchRequest()
.WithCollectionName(collection_name)
.WithAnnsField("dense")
.WithRerank(function_score)
.WithLimit(10)
.AddOutputField("title")
.AddOutputField("publish_time")
.AddFloatVector(your_query_vector)
.WithConsistencyLevel(milvus::ConsistencyLevel::STRONG);
milvus::SearchResponse response;
auto status = client->Search(request, response);
if (!status.IsOk()) {
std::cout << status.Message() << std::endl;
}
import { MilvusClient } from "@zilliz/milvus2-sdk-node";
const milvusClient = new MilvusClient("YOUR_CLUSTER_ENDPOINT");
const result = await milvusClient.search({
collection_name: "collection_name",
data: [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]], // Replace with your query vector
anns_field: "dense",
limit: 10,
output_fields: ["title", "publish_time"],
rerank: rerank, // Apply the decay ranker
consistency_level: "Strong",
});
curl -s YOUR_CLUSTER_ENDPOINT/v2/vectordb/entities/search \
-H "Authorization: Bearer YOUR_CLUSTER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"collectionName": "collection_name",
"data": [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]],
"annsField": "dense",
"limit": 10,
"outputFields": ["title", "publish_time"],
"functionScore": {
"functions": [{
"name": "news_recency",
"type": "Rerank",
"inputFieldNames": ["publish_time"],
"outputFieldNames": [],
"params": {
"reranker": "decay",
"function": "exp",
"origin": 1790872778,
"offset": 10800,
"decay": 0.5,
"scale": 86400
}
}],
"params": {}
},
"consistencyLevel": "Strong"
}'