高斯衰减
高斯衰减,也称为正态衰减,能对搜索结果进行最自然的调整。就像人类的视觉会随着距离逐渐模糊一样,高斯衰减会创建一条平滑的钟形曲线,随着项目远离理想点,逐渐降低其相关性。当你希望实现一种平衡的衰减,既不会对刚好超出首选范围的项目进行大举罚分,又能显著降低远距离项目的相关性时,这种方法是理想之选。
与其他 Decay Ranker 不同:
-
指数衰减起初下降迅速,产生更强的初始罚分
-
线性衰减以恒定速率下降直至归零,形成明确的截止点
高斯衰减提供了一种更加平衡、直观的方法,让用户感觉自然。
何时使用高斯衰减
高斯衰减在以下方面特别有效:
| 用例 | 示例 | 高斯衰减为何效果良好 |
|---|---|---|
| 基于位置的搜索 | 餐厅查找器、店铺定位器 | 模拟人类对距离相关性的自然感知 |
| 内容推荐 | 基于发表日期的文章推荐 | 随着内容老化,相关性逐渐下降 |
| 产品列表 | 价格接近目标的商品 | 当价格偏离目标时,相关性平稳下降 |
| 专业匹配 | 寻找有相关经验的专业人士 | 对经验相关性的平衡评估 |
如果你的应用程序需要一种自然的相关性衰减感觉,而不采用严厉的惩罚或严格的截断,高斯衰减可能是你的最佳选择。
钟形曲线原理
高斯衰减会产生一条平滑的钟形曲线,随着与理想点的距离增加,相关性逐渐降低。这种分布以数学家卡尔·弗里德里希·高斯的名字命名,在自然界和统计学中经常出现。这也说明了为什么它在人类的感知中如此直观。

上图展示了高斯衰减如何影响移动搜索应用中的餐厅排名:
-
origin(0公里):又被称为原点,表示您当前的位置。此处相关性达到最大值(1.0)。 -
offset(±300米):又被称为偏移量,表示您周围的“完美得分区域”——300米范围内的所有餐厅均保持满分相关性得分(1.0),确保非常近的选项不会因微小的距离差异而被不必要地扣分。 -
scale(±2公里):又被称为关注范围,表示相关性降至衰减值的距离——距离正好2公里的餐厅,其相关性得分减半(0.5)。 -
decay(0.5):又被称为衰减值,表示在关注范围边缘的得分——此参数本质上控制得分随距离衰减的速度。
从曲线中可以看出,距离超过2公里的餐厅的相关性持续下降,但永远不会完全降至零。即使是4 - 5公里外的餐厅也保留了一定的最低相关性,这使得优质但距离较远的餐厅仍能出现在你的搜索结果中(尽管排名较低)。
这种行为模仿了人们自然思考距离相关性的方式——人们倾向于选择附近的地方,但为了特别的选择,也愿意走得更远。
公式
计算高斯衰减得分的数学公式为:
其中:
用通俗易懂的语言来解释就是:
-
计算字段值离原点的距离:
-
减去偏移量(如果有的话),但结果永远不能小于零:
-
将这个调整后的距离平方:
-
除以$2\sigma^2$,它是根据您的缩放和衰减参数计算得出的
-
取负指数,它会给出一个介于 0 和 1 之间的值:
该 计算将您的不衰减范围和衰减参数转换为高斯分布的均方差平方。这就是该函数呈现其特征钟形的原因。
使用高斯衰减
高斯衰减可应用于 Zilliz Cloud 中的标准向量搜索和混合搜索操作。以下是实现此功能的关键代码片段。
在使用 Decay Ranker 之前,你必须首先创建一个包含适当数字类型字段(如时间戳、距离等)的集合,这些字段将用于衰减计算。有关包括 Collection 设置、 Schema 定义和数据插入的完整工作示例,请参考教程:实现基于时间的搜索结果重排。
创建一个 Decay Ranker
在您的集合设置了一个数字字段(此处,以 distance 为例,单位为米)后,创建一个使用高斯衰减的 Decay Ranker:
- Python
- Java
- Go
- Rust
- C++
- NodeJS
- cURL
from pymilvus import Function, FunctionType
# Create a Gaussian decay ranker for location-based restaurant search
rerank = Function(
name="restaurant_distance_decay", # Function identifier
input_field_names=["distance"], # Numeric field for distance in meters
function_type=FunctionType.RERANK, # Function type. Must be RERANK
params={
"reranker": "decay", # Specify decay reranker
"function": "gauss", # Choose Gaussian decay
"origin": 0, # Your current location (0 meters)
"offset": 300, # 300m no-decay zone
"decay": 0.5, # Half score at scale distance
"scale": 2000 # 2 km scale (2000 meters)
}
)
import io.milvus.v2.service.vector.request.ranker.DecayRanker;
DecayRanker rerank = DecayRanker.builder()
.name("restaurant_distance_decay")
.inputFieldNames(Collections.singletonList("distance"))
.function("gauss")
.origin(0)
.offset(300)
.decay(0.5)
.scale(2000)
.build();
rerank := entity.NewFunction().
WithName("restaurant_distance_decay").
WithType(entity.FunctionTypeRerank).
WithInputFields("distance").
WithParam("reranker", "decay").
WithParam("function", "gauss").
WithParam("origin", "0").
WithParam("offset", "300").
WithParam("decay", "0.5").
WithParam("scale", "2000")
use milvus::v2::prelude::*;
let rerank = Function::new()
.name("restaurant_distance_decay")
.function_type(FunctionType::Rerank)
.input_fields(vec!["distance"])
.param("reranker", "decay")
.param("function", "gauss")
.param("origin", "0")
.param("offset", "300")
.param("decay", "0.5")
.param("scale", "2000");
auto rerank = std::make_shared<milvus::DecayRerank>("restaurant_distance_decay");
rerank->AddInputFieldName("distance");
rerank->SetFunction("gauss");
rerank->SetOrigin(0);
rerank->SetScale(2000);
rerank->SetOffset(300);
rerank->SetDecay(0.5);
import { FunctionType } from "@zilliz/milvus2-sdk-node";
const rerank = {
name: "restaurant_distance_decay",
input_field_names: ["distance"],
type: FunctionType.RERANK,
params: {
reranker: "decay",
function: "gauss",
origin: 0,
offset: 300,
decay: 0.5,
scale: 2000,
},
};
rerank='{
"name": "restaurant_distance_decay",
"type": "Rerank",
"inputFieldNames": ["distance"],
"params": {
"reranker": "decay",
"function": "gauss",
"origin": 0,
"offset": 300,
"decay": 0.5,
"scale": 2000
}
}'
在标准向量搜索中使用
定义 Decay Ranker 后,您可以在搜索请求中通过将其传递给 ranker 参数来应用它:
- Python
- Java
- Go
- Rust
- C++
- NodeJS
- cURL
# Apply decay ranker to restaurant 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=["name", "cuisine", "distance"], # Fields to return
ranker=rerank, # Apply the decay ranker
consistency_level="Strong"
)
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;
import java.util.Arrays;
import java.util.Collections;
SearchReq searchReq = SearchReq.builder()
.collectionName(COLLECTION_NAME)
.data(Collections.singletonList(new FloatVec(new float[]{0.1f, 0.2f, 0.3f, 0.4f}))) // Replace with your query vector
.annsField("dense")
.limit(10)
.outputFields(Arrays.asList("name", "cuisine", "distance"))
.functionScore(FunctionScore.builder()
.addFunction(rerank)
.build())
.consistencyLevel(ConsistencyLevel.STRONG)
.build();
SearchResp searchResp = client.search(searchReq);
import "github.com/milvus-io/milvus/client/v3/entity"
results, err := client.Search(ctx, milvusclient.NewSearchOption(
"restaurant_db", 10,
[]entity.Vector{entity.FloatVector{0.1, 0.2, 0.3, 0.4}}). // Replace with your query vector
WithANNSField("dense").
WithOutputFields("name", "cuisine", "distance").
WithFunctionReranker(rerank).
WithConsistencyLevel(entity.ClStrong))
if err != nil {
fmt.Println(err.Error())
}
use milvus::v2::prelude::*;
let search_req = SearchRequest::builder()
.collection_name("restaurant_db")
.vector_field("dense")
.vectors(SearchVectors::Float(vec![vec![0.1f32, 0.2, 0.3, 0.4]]))
.limit(10)
.output_fields(vec!["name", "cuisine", "distance"])
.rerank(FunctionScore::new().add_function(rerank))
.consistency_level(ConsistencyLevel::Strong)
.build()?;
let res = client.search(search_req).await?;
println!("{:?}", res.results());
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("name")
.AddOutputField("cuisine")
.AddOutputField("distance")
.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;
}
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: ["name", "cuisine", "distance"],
rerank: rerank,
consistency_level: "Strong",
});
export CLUSTER_ENDPOINT="YOUR_CLUSTER_ENDPOINT"
export TOKEN="YOUR_CLUSTER_TOKEN"
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d '{
"collectionName": "restaurant_db",
"data": [[0.1, 0.2, 0.3, 0.4]],
"annsField": "dense",
"limit": 10,
"outputFields": ["name", "cuisine", "distance"],
"functionScore": {
"functions": [
{
"name": "restaurant_distance_decay",
"type": "Rerank",
"inputFieldNames": ["distance"],
"params": {"reranker": "decay", "function": "gauss", "origin": 0, "offset": 300, "decay": 0.5, "scale": 2000}
}
]
},
"consistencyLevel": "Strong"
}'