基于二分图匹配算法的商家保量系统实践

2024-03-04 18:04

本文主要是介绍基于二分图匹配算法的商家保量系统实践,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、背景

商家扶持保量系统的业务功能,是保证重点签约商家能够在当天拿到预期的流量。比较简单的保量方案是通过pid算法控制,即根据每个商家的放量进度输出pid系数(比如值域[1, 16]),乘以pxtr排序公式后参与最终排序,pid系数越高排序靠前的概率更高,该排序算法弊端是:

  1. 仅考虑商家的流量缺口,缺口越大透出概率越大,pxtr作用较小,流量匹配效率低下
  2. 商家给量不均匀,可能在商家直播的某个时间段给过高流量,导致商家效率不足

本文重点介绍的二分图匹配算法能够做到在匹配窗口内的流量分配全局最优,兼顾流量的分发效率和商家的流量需求,预期大幅提升扶持流量的整体效率,从而带动推荐系统整体效率提升。

二、匹配算法介绍

1、定义凸优化问题

min ⁡ − ∑ i , j w i , j V j x i , j + λ 2 ∑ i , j ∥ x i , j − p j ∥ 2 s . t . ∑ j x i , j ≤ 1 , ∀ i ( 1 ) ∑ i x i , j ≥ d j , ∀ i ( 2 ) x i , j ≥ 0 , ∀ i , j ( 3 ) \min{-\sum_{i,j}w_{i,j}V_j x_{i,j}} + \frac{\lambda}{2} \sum_{i,j}\|x_{i,j} - p_j\|^2\\ \begin{aligned} s.t. \quad \sum_j x_{i,j} & \le 1,\quad \forall i &(1)\\ \sum_i x_{i,j} & \ge d_j,\quad \forall i & (2)\\ x_{i,j} & \ge0,\quad \forall i,j & (3) \end{aligned} mini,jwi,jVjxi,j+2λi,jxi,jpj2s.t.jxi,jixi,jxi,j1,idj,i0,i,j(1)(2)(3)

其中:
i , j i,j i,j:表示第i次请求和第j个商家
w i , j w_{i,j} wi,j:表示第i次请求中用户对第j个商家的价值贡献,一般为pxtr的公式组合
V j V_j Vj:表示第j个商家的高光系数,亦即该商家的优先级
x i , j x_{i,j} xi,j:表示第i次请求透出第j个商家的概率,为待求解变量
d j d_j dj:表示第j个商家提报流量
p j = d j ∑ j d j p_{j} = \frac{d_j}{\sum_j d_j} pj=jdjdj:表示第j个商家平均能分配流量的比例,可以控制流量分配的平滑程度

注意:约束条件中
第一个为一次请求中只能选择最多给一个商家透出
第二个为对一个商家的累计曝光需要超过提报量
第三个为透出概率大于0

2、参数推导

凸优化问题转化为拉格朗日对偶问题
min ⁡ − ∑ i , j w i , j V j x i , j + λ 2 ∑ i , j ∥ x i , j − p j ∥ 2 + ∑ i β i ( ∑ j x i , j − 1 ) − ∑ j α j ( ∑ i x i , j − d j ) − ∑ i , j γ i , j x i , j \min{-\sum_{i,j}w_{i,j}V_j x_{i,j}} + \frac{\lambda}{2} \sum_{i,j}\|x_{i,j} - p_j\|^2 + \sum_{i}\beta_i \big(\sum_{j}x_{i,j} - 1\big) - \sum_{j}\alpha_j \big(\sum_{i}x_{i,j} - d_j\big) - \sum_{i,j}\gamma_{i,j}x_{i,j} mini,jwi,jVjxi,j+2λi,jxi,jpj2+iβi(jxi,j1)jαj(ixi,jdj)i,jγi,jxi,j

求导等于0得到
x i , j = ( α j − β i + w i , j V j + γ i , j ) / λ + p j x_{i,j} = (\alpha_j-\beta_i+w_{i,j}V_j + \gamma_{i,j})/\lambda + p_j xi,j=(αjβi+wi,jVj+γi,j)/λ+pj

根据KKT条件,要么 γ i , j = 0 \gamma_{i,j} = 0 γi,j=0,要么 x i , j = 0 x_{i,j} = 0 xi,j=0,因此上式可以简化为
x i , j = max ⁡ ( 0 , α j − β i + w i , j V j λ + p j ) = g i , j ( α j − β i ) \begin{aligned} x_{i,j} &= \max(0, \frac{\alpha_j-\beta_i+w_{i,j}V_j}{\lambda} + p_j) \\ &= g_{i,j}(\alpha_j - \beta_i) \end{aligned} xi,j=max(0,λαjβi+wi,jVj+pj)=gi,j(αjβi)

3、离线求解

不用考虑剩余流量(因为本方案没有人群限制,即对所有用户生效),利用坐标梯度下降法求解 α j \alpha_j αj β i \beta_i βi
1、对所有 j j j,初始化 α j = 0 \alpha_j = 0 αj=0

2、对于所有的流量 i i i,假设 α j \alpha_j αj已知,计算 β i \beta_i βi,使之满足
∑ j x i , j = 1 \sum_j x_{i,j} = 1 jxi,j=1
代入表达式即
∑ j g i , j ( α j − β i ) = 1 \sum_j g_{i,j}(\alpha_j - \beta_i) = 1 jgi,j(αjβi)=1
否则 β i \beta_i βi无解,设置为0

3、对于所有的商家 j j j,假设 β i \beta_i βi已知,计算 α j \alpha_j αj,使之满足
∑ i x i , j = d j \sum_i x_{i,j} = d_j ixi,j=dj
代入表达式即
∑ i g i , j ( α j − β i ) = d j \sum_i g_{i,j}(\alpha_j - \beta_i) = d_j igi,j(αjβi)=dj
否则 α j \alpha_j αj无解,设置为0

4、重复上述2、3步骤,直到 α j \alpha_j αj β i \beta_i βi收敛(实际限制最多迭代轮数即可)
注:在线存储只保存 α j \alpha_j αj

4、在线匹配

1、对于所有的流量 i i i,利用离线计算的 α j \alpha_j αj计算 β i \beta_i βi,使之满足
∑ j x i , j = 1 \sum_j x_{i,j} = 1 jxi,j=1
代入表达式即
∑ j g i , j ( α j − β i ) = 1 \sum_j g_{i,j}(\alpha_j - \beta_i) = 1 jgi,j(αjβi)=1
否则 β i \beta_i βi无解,设置为0

2、利用 α j \alpha_j αj β i \beta_i βi计算
x i , j = g i , j ( α j − β i ) = max ⁡ ( 0 , ( α j − β i + w i , j V j ) / λ + p j ) \begin{aligned} x_{i,j} &= g_{i,j}(\alpha_j - \beta_i) \\ &=\max(0, (\alpha_j-\beta_i+w_{i,j}V_j)/\lambda + p_j) \end{aligned} xi,j=gi,j(αjβi)=max(0,(αjβi+wi,jVj)/λ+pj)
并使用 x i , j x_{i,j} xi,j作为商家透出的概率,降序排列截取top

三、保量系统功能实践

系统分为三个主要模块
1、实时更新服务:获取商家实时获取曝光量,从而计算出该商家缺口曝光量,用来更新流量额度d和分配比例p
2、离线参数求解服务:记录每次请求模型预估组合打分 w i , j w_{i,j} wi,j,在线累计10w次请求后执行离线求解过程得到 α j \alpha_j αj β i \beta_i βi,最终只保存 α j \alpha_j αj
3、在线计算服务:处理实时请求时,使用离线求解的 α j \alpha_j αj和传入的 w i , j w_{i,j} wi,j根据公式计算得到 β i \beta_i βi,再根据公式计算得到 x i , j x_{i,j} xi,j作为商家透出的概率,降序排列截取top

整体流程如下:
在这里插入图片描述

1、离线求解代码实现
根据第二节的第3步可以看到,求解的公式都是带有max操作的一次方程,参考Solve equations using the max
function,可以转化为分段闭式解求解。迭代循环代码如下

for (int iter = 1; iter <= 20; ++iter) {LOG(INFO) << "begin iter=" << iter;// update betafor (int i = 0; i < user_num; ++i) {betas[i] = SolveBeta(alphas, user_flow_[i], probabilities, photo_num);}// update alphafor (int j = 0; j < photo_num; ++j) {alphas[j] = SolveAlpha(betas, photo_flow_[j], photo_allocation_[j], probabilities[j], demands[j], user_num);}// ensure non-negative alpha and betaint64_t min_alpha_beta = INT64_MAX;for (int i = 0; i < user_num; ++i) {min_alpha_beta = std::min(betas[i], min_alpha_beta);}for (int j = 0; j < photo_num; ++j) {min_alpha_beta = std::min(alphas[j], min_alpha_beta);}// alpha beta >= 0 ensure min_alpha_beta >= 0for (int i = 0; i < user_num; ++i) {betas[i] -= min_alpha_beta;}for (int j = 0; j < photo_num; ++j) {alphas[j] -= min_alpha_beta;}
}

求解 b e t a beta beta a l p h a alpha alpha的代码如下

float SolveBeta(const float *alphas, const float *weights, float *probabilities, size_t photo_num) {std::vector<float> target;for (size_t j = 0; j < photo_num; ++j) {if (probabilities[j] <= 0 || weights[j] <= 0) { //  user_flow_矩阵稀疏求解continue;}float alpha_score = alphas[j] + weights[j] + OBJECT_LAMBDA * probabilities[j];target.push_back(alpha_score);}float beta = 0.0;std::sort(target.begin(), target.end(), std::greater<float>());float target_sum = 0.0;float coef_sum = 0.0;int64_t solve_success = 0;for (size_t j = 0; j < target.size(); ++j) {target_sum += target[j];coef_sum += 1.0;float tmp_beta = (target_sum - OBJECT_LAMBDA) / coef_sum;if (tmp_beta > 0 && tmp_beta <= target[j]){beta = tmp_beta;solve_success = 1;break;}}return beta;
}float SolveAlpha(const float *betas, const float *weights, float probability, float demand, size_t user_num) {if (probability <= 0 || demand <= 0) { //  photo_flow_矩阵稀疏求解return 0;}std::vector<float> target;for (size_t i = 0; i < user_num; ++i) {if (weights[i] <= 0) { //  user_flow_矩阵稀疏求解continue;}float beta_score = weights[i] - betas[i] + OBJECT_LAMBDA * probability;target.push_back(beta_score);}float alpha = 0.0;std::sort(target.begin(), target.end(), std::greater<float>());float target_sum = 0.0;float coef_sum = 0.0;int64_t solve_success = 0;for (size_t i = 0; i < target.size(); ++i) {target_sum += target[i];coef_sum += 1.0;float tmp_alpha = (OBJECT_LAMBDA * demand / MAX_SCORE - target_sum) / coef_sum;if (tmp_alpha > 0 && tmp_alpha >= -target[i]){alpha = tmp_alpha;solve_success = 1;break;}}return alpha;
}

2、在线求解代码实现
在线求解只需要根据保存的 α \alpha α求解 β \beta β即可,流程和离线求解一致

float OnlineSolveBeta(const float *alphas, const float *weights, float *probabilities, size_t candidate_size) {std::vector<float> target;for (size_t j = 0; j < candidate_size; ++j) {if (probabilities[j] <= 0 || weights[j] <= 0) { //  user_flow_矩阵稀疏求解continue;}float alpha_score = alphas[j] + weights[j] + OBJECT_LAMBDA * probabilities[j];target.push_back(alpha_score);}float beta = 0.0;std::sort(target.begin(), target.end(), std::greater<float>());float target_sum = 0.0;float coef_sum = 0.0;int64_t solve_success = 0;for (size_t j = 0; j < target.size(); ++j) {target_sum += target[j];coef_sum += 1.0;float tmp_beta = (target_sum - OBJECT_LAMBDA) / coef_sum;if (tmp_beta > 0 && tmp_beta <= target[j]){beta = tmp_beta;solve_success = 1;break;}}return beta;
}

四、参考

https://segmentfault.com/a/1190000021419023
https://mp.weixin.qq.com/s/2VejGdsZrCxB8pD1y7Bqhg
https://zhuanlan.zhihu.com/p/348022042
https://zhuanlan.zhihu.com/p/123187987
https://zhuanlan.zhihu.com/p/36051733
https://github.com/wangrunjie/SHALE/tree/94b47ccbe4b4cabe233c9512350969cda57f2e08
https://math.stackexchange.com/questions/145458/solve-equations-using-the-max-function

这篇关于基于二分图匹配算法的商家保量系统实践的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/773991

相关文章

SpringBoot简单整合ElasticSearch实践

《SpringBoot简单整合ElasticSearch实践》Elasticsearch支持结构化和非结构化数据检索,通过索引创建和倒排索引文档,提高搜索效率,它基于Lucene封装,分为索引库、类型... 目录一:ElasticSearch支持对结构化和非结构化的数据进行检索二:ES的核心概念Index:

Python数据验证神器Pydantic库的使用和实践中的避坑指南

《Python数据验证神器Pydantic库的使用和实践中的避坑指南》Pydantic是一个用于数据验证和设置的库,可以显著简化API接口开发,文章通过一个实际案例,展示了Pydantic如何在生产环... 目录1️⃣ 崩溃时刻:当你的API接口又双叒崩了!2️⃣ 神兵天降:3行代码解决验证难题3️⃣ 深度

C++ move 的作用详解及陷阱最佳实践

《C++move的作用详解及陷阱最佳实践》文章详细介绍了C++中的`std::move`函数的作用,包括为什么需要它、它的本质、典型使用场景、以及一些常见陷阱和最佳实践,感兴趣的朋友跟随小编一起看... 目录C++ move 的作用详解一、一句话总结二、为什么需要 move?C++98/03 的痛点⚡C++

C++简单日志系统实现代码示例

《C++简单日志系统实现代码示例》日志系统是成熟软件中的一个重要组成部分,其记录软件的使用和运行行为,方便事后进行故障分析、数据统计等,:本文主要介绍C++简单日志系统实现的相关资料,文中通过代码... 目录前言Util.hppLevel.hppLogMsg.hppFormat.hppSink.hppBuf

MySQL存储过程实践(in、out、inout)

《MySQL存储过程实践(in、out、inout)》文章介绍了数据库中的存储过程,包括其定义、优缺点、性能调校与撰写,以及创建和调用方法,还详细说明了存储过程的参数类型,包括IN、OUT和INOUT... 目录简述存储过程存储过程的优缺点优点缺点存储过程的创建和调用mysql 存储过程中的关键语法案例存储

Java 的ArrayList集合底层实现与最佳实践

《Java的ArrayList集合底层实现与最佳实践》本文主要介绍了Java的ArrayList集合类的核心概念、底层实现、关键成员变量、初始化机制、容量演变、扩容机制、性能分析、核心方法源码解析、... 目录1. 核心概念与底层实现1.1 ArrayList 的本质1.1.1 底层数据结构JDK 1.7

JDK21对虚拟线程的几种用法实践指南

《JDK21对虚拟线程的几种用法实践指南》虚拟线程是Java中的一种轻量级线程,由JVM管理,特别适合于I/O密集型任务,:本文主要介绍JDK21对虚拟线程的几种用法,文中通过代码介绍的非常详细,... 目录一、参考官方文档二、什么是虚拟线程三、几种用法1、Thread.ofVirtual().start(

从基础到高级详解Go语言中错误处理的实践指南

《从基础到高级详解Go语言中错误处理的实践指南》Go语言采用了一种独特而明确的错误处理哲学,与其他主流编程语言形成鲜明对比,本文将为大家详细介绍Go语言中错误处理详细方法,希望对大家有所帮助... 目录1 Go 错误处理哲学与核心机制1.1 错误接口设计1.2 错误与异常的区别2 错误创建与检查2.1 基础

深入理解Mysql OnlineDDL的算法

《深入理解MysqlOnlineDDL的算法》本文主要介绍了讲解MysqlOnlineDDL的算法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小... 目录一、Online DDL 是什么?二、Online DDL 的三种主要算法2.1COPY(复制法)

springboot依靠security实现digest认证的实践

《springboot依靠security实现digest认证的实践》HTTP摘要认证通过加密参数(如nonce、response)验证身份,避免明文传输,但存在密码存储风险,相比基本认证更安全,却因... 目录概述参数Demopom.XML依赖Digest1Application.JavaMyPasswo