点击率预估模型发展到现在,大家基本默认一套固定参数打天下:不管来的是新用户还是老用户,不管用户在刷首页还是逛详情页,模型最深层的那几层全连接或者交叉层的权重都是同一份。这个假设在流量稀疏、特征维度爆炸的推荐场景下,越来越显得不够用。APG(Adaptive Parameter Generation)这类自适应参数生成网络的思路,就是把这个"静态参数"的限制打破——让模型根据当前样本的用户行为、上下文信息,动态生成一套更贴合该样本的参数来打分。这篇文章我会从问题根源讲起,拆解APG的生成机制、训练要点,也会把我在复现和实际调优中踩过的坑一并整理出来,给想动手尝试的读者一条能走得通的路线。
1. 静态参数模型的天花板:为什么同一套权重撑不住所有样本
1.1 一个样本一套打分逻辑的需求从哪来
做CTR预估的人都知道,模型输入侧做了大量特征交叉,比如用户id和商品id的embedding点积、多阶特征组合,目的就是让不同兴趣的用户、不同类型的商品在特征空间里尽量拉开距离。但问题是,这些交叉只发生在特征层面,模型内部的网络参数仍然是全局共享的。
举个实际例子。某个高频活跃用户,历史点击集中在数码3C类目,她的行为序列特征是稳定的、低方差的。另一个新注册用户,只有零星几次美妆类目曝光,特征分布稀疏且噪声大。这两类样本进入同一个全连接层,用的是完全相同的权重矩阵W和偏置b。模型只能通过特征值的变化来表达差异,而特征值在稀疏场景下往往承载不了那么强的表达能力——这也正是为什么深度模型在CTR任务上经常出现"特征做得越花,上层网络越成瓶颈"的尴尬。
APG想解决的问题就藏在这里:能不能让网络参数本身也随输入变化?对高频用户,生成一套强调历史行为稳定性的参数;对稀疏新用户,生成一套更依赖先验泛化信息的参数。参数层面的自适应,比单纯特征层面的自适应,理论上能给模型带来更细粒度的表达能力。
1.2 APG在方法谱系中的定位
在介绍APG之前,先把它放到整个"动态网络"家族里看会更清楚。
一类方式是MoE(Mixture of Experts),通过门控网络在不同专家网络之间做软选择,本质上是离散组合,专家的参数还是固定的,门控只决定加权系数。另一类是HyperNetwork,用一个辅助网络输出主网络的权重,这个思路在图像生成和元学习领域用得比较多,但直接搬到CTR场景会遇到开销问题——CTR模型的embedding表和上层网络都很大,全量生成参数在计算和存储上都扛不住。
APG的聪明之处在于,它把参数生成限定在模型最上层的少量全连接层或交互层上,并且利用"参数矩阵往往具有低秩结构"这一观察,把生成过程设计成若干个小矩阵的组合。这样既保留了输入自适应的能力,又把生成成本控制在一个可以接受的范围内。在论文的实际设计中,APG通常输出目标层的权重增量而非完整权重,主网络的原始参数仍然保留,生成网络只负责在原始参数上加一个自适应偏移量。这种"残差式"设计有一个很直接的好处:如果生成网络在某类样本上效果不好,至少不会破坏预训练参数的基线能力。
2. APG核心机制拆解:参数生成器到底在算什么
2.1 从输入特征到参数矩阵的一条通路
APG的整体结构不复杂:主干网络仍然是常见的embedding层加深层网络,只是在某个特定层旁边挂了一个生成器。这个生成器的输入通常取当前样本的交叉特征或用户侧特征聚合向量,输出经过一系列变换后,变成与目标层权重同尺寸的增量矩阵。
具体拆解一下。假设主干网络的第l层是一个全连接层,权重为W_l(维度d_in × d_out),偏置为b_l。APG的生成器接收一个向量g,这个g一般来自两个地方:一个是原始输入特征经过attention或简单池化得到的向量,另一个是主干网络第l层之前的隐层输出。生成器内部先把g映射为一个中间特征,然后通过两个分支分别生成W_l的增量ΔW_l和b_l的增量Δb_l。
这里的关键工程细节在于,ΔW_l如果是完整矩阵,那生成成本就和权重本身一样大,没有意义。所以APG采用低秩分解:ΔW_l = A × B,其中A的维度是d_in × r,B的维度是r × d_out,r是一个远小于d_in和d_out的秩参数。生成器只需要输出A和B,两个小矩阵,再乘回得到增量。这样参数量从d_in × d_out降到了(d_in + d_out) × r,在常规CTR模型里,r取8到32就够用。
2.2 不同样本如何得到不同参数:映射函数的设计逻辑
生成器的映射函数选型会直接影响最终效果。我见过几种常见做法,按效果和稳定性的综合表现排序:
- 单层线性映射加激活:简单,训练稳定,但表达增益有限。
- 两层MLP:效果最好,但要注意中间层维度不能太小,否则会变成信息瓶颈。
- 带LayerNorm的映射:对训练稳定性帮助明显,尤其当输入特征分布差异大的时候。
我自己复现时首选两层MLP加LayerNorm。第一层把输入g从d_g维映射到4r或8r维的隐空间,激活函数用GELU,第二层分别输出A和B的展平向量。注意A和B的输出激活函数必须用线性或者tanh,不能用ReLU——因为ReLU会把负值截断,而权重增量本身是需要允许负值的,强行加ReLU相当于给参数空间加了一个约束边界,会显著降低自适应效果。
还有一个很容易忽略的点:生成器的输入g必须包含足够的样本差异性信息。如果g只取了用户id的embedding,那模型学到的其实还是按用户分群的参数切换;如果g里包含行为序列的聚合向量和上下文特征,参数生成的细粒度才会真正落到"每个请求"上。我建议至少把以下三类信息拼进g:用户侧embedding均值池化、当前曝光物品的embedding、二者内积加一个上下文特征。这样g既包含了单侧信息,也包含了交叉信号。
2.3 生成参数如何参与前向计算
假设目标层原来的计算是h_out = h_in × W_l + b_l,加入APG之后变成:
h_out = h_in × (W_l + ΔW_l) + (b_l + Δb_l)
也就是把生成出来的增量加到原始权重上再算。这一步骤在PyTorch里可以直接通过把ΔW_l和W_l相加的F.linear实现,不需要改主干网络的forward逻辑。反向传播时,梯度会同时流向W_l和生成器,因此是端到端联合训练的。
训练初期有个值得注意的行为:生成器刚初始化时,ΔW_l接近全零,整个网络等价于原始的静态参数模型,这样相当于从一个可靠的起点开始学习,不会因为参数突变导致训练崩掉。实际操作时,我会把生成器最后一层的权重初始化到极小值,比如scale=0.01的均匀分布,确保初始增量接近零。这个细节对训练稳定性的贡献比重很大。
3. 训练调优中的关键坑:梯度、稳定性和正则
3.1 共享底层特征导致的梯度耦合问题
APG生成器的输入g如果取自主干网络的隐层,那么生成器会和主干网络共享一部分底层特征。这在理论上没问题,但实践中会出现梯度耦合:底层特征的更新既要服务于主干网络的预测目标,又要服务于生成器的参数生成目标,两个目标如果步调不一致,底层特征会被拉向一个奇怪的平衡点,表现为loss震荡。
我踩过这个坑之后的解法是:在g进入生成器之前加stop-gradient操作,让生成器看到的输入特征是当前轮次的固定值,不参与梯度回传。这样底层特征只对预测目标负责,生成器只在给定特征下做参数生成,职责清晰,收敛速度反而更快。
当然,stop-gradient会损失一部分底层特征和生成器之间的协同优化能力。如果想要两者兼顾,可以用一个折中方案:g的路径上只允许梯度经过一个较窄的瓶颈层,限制生成器对底层特征的"影响半径",本质上是给梯度耦合加一个约束。
3.2 生成参数的正则化选择
ΔW_l如果不加任何正则,训练后期会出现一个现象:生成参数的标准差越来越大,主干网络本身的参数反而被"架空",模型整体泛化能力退化为对训练样本的机械记忆。
我的做法是加一个L2正则,但作用对象不是ΔW_l本身,而是A和B两个因子矩阵。原因是A和B的乘积对正则的响应是二次的,如果直接正则ΔW_l,梯度计算会绕一圈,不够直接。正则系数通常设在1e-5到1e-4之间,具体要看数据量。数据量大的时候可以放小一点,让自适应能力更充分;数据量小的时候加大正则,防止生成器过拟合到训练集上的参数模式。
另外一个从实验结果中验证过的技巧是:对Δb_l(偏置增量)不加正则。偏置增量维度小,影响面有限,而且它承担了样本级的归一化偏移,加了正则反而会限制模型对不同样本分布的适应能力。
3.3 学习率与训练策略的适配
APG生成器的参数量相比主干网络小很多,如果用同一个学习率更新,生成器经常处于"学不动"或"学过头"两个极端。我在实验中观察到,生成器需要一个相对更大的学习率,一般为主干网络学习率的2到3倍,才能让参数增量在训练前期明显变化起来。
配套使用的学习率计划也要调整。如果主干网络用余弦退火,生成器建议用阶梯衰减,而且在退火后期要提前冻结生成器——只更新主干网络的参数。理由很简单:训练后期,主干网络已经收敛到一个稳定的特征表达空间,此时生成器还在大幅度调整参数,会破坏已经很优秀的特征分布,导致验证集指标先升后降。
具体到训练流程上,我的推荐方案是:
- 前30%的训练轮次,主干网络和生成器同时开训,生成器学习率是主干的2倍;
- 中间30%轮次,两个网络共用一个学习率,让生成器的调整幅度自然回落;
- 最后40%轮次,冻结生成器,只微调主干网络和embedding层。
4. 模型集成位置选在哪:不是所有层都值得做自适应
4.1 不同网络层的自适应收益差异
不少读者会想当然地认为,APG应该加在每一层,加得越多越好。实际测试下来完全不是这样。我在一个标准的embedding+MLP结构上做了消融:只加最后一层全连接,AUC提升约0.4个千分点;加倒数第二层,提升约0.2个千分点;把所有全连接层都加上,提升反而下降到0.15个千分点。
这个现象其实有清晰的解释:靠近输入侧的层,主要在做特征组合和抽象,参数的自适应需要非常复杂的条件映射才能有意义,而简单MLP生成器学不出这种复杂映射,反而引入噪声。越靠近输出侧,神经元数量越少,每个参数对应的语义越"全局",此时参数随样本变化带来的增益越直接。
实操建议:APG优先加在最后一层全连接之前的那次线性变换上,也就是输出logits前的那个隐层。如果模型结构里还有类似FM的二阶交互层,可以在交互结果的加权求和处也挂一个生成器,效果通常也不错。
4.2 和注意力机制共存时的冲突与化解
现在很多CTR模型内部本身就有attention结构,比如self-attention处理行为序列,target attention处理用户与商品的交叉。APG加入后,要注意一个潜在冲突:attention的权重是输入自适应的(这本身就是一种软参数自适应),而APG生成的是硬参数自适应。两者叠加会带来一种"双重自适应"的冗余。
我在实测中发现,如果模型里已经有一个强attention层,APG带来的增量会变小,这不是bug,而是两者确实存在能力重叠。这种情况下,可以把APG的输入g从原始特征换成attention层的输出表示,让参数生成器专注于"在attention已经完成的软选择基础上,再做参数级的硬调整"。这样两者的分工就清晰了:attention负责样本内部的信息选择,APG负责样本之间的参数差异化。
4.3 生成器容量与数据规模的匹配
APG的生成器不是越大越好,它的容量必须和训练数据规模匹配。在千万级样本上,生成器隐层取256维就够了;数据量到亿级,可以放宽到1024维。比较反直觉的是,在数据规模小的场景下,增大生成器容量会带来严重的过拟合——生成器会把某些样本特有的参数模式记下来,导致线上推理时遇到分布外的样本,生成的参数明显偏离合理范围。
一个简单的检测方法:训练结束后,统计ΔW_l的Frobenius范数在训练集和验证集上的分布。如果验证集上的范数均值明显偏离训练集,说明生成器过拟合了,需要降容量或者加正则。如果两者接近,说明生成器学到了可泛化的参数生成规律。
5. 工程落地:从离线实验到线上推理的优化路径
5.1 推理阶段的增量计算开销控制
APG最让人担心的就是线上延迟。每来一个请求都要跑一遍生成器,产生ΔW_l,这确实比静态模型多了一次前向计算。但实际优化下来,开销比大部分人预想的要小。
生成器的计算量主要由输入g的维度和秩r决定。假设g维度是128,r是16,目标层维度是256×64,那么生成器的FLOPs大约只有主网络一个全连接层的十分之一。关键优化点在于不要显式构造ΔW_l的完整矩阵再和W_l相加——直接做low-rank的矩阵乘法分解:
h_out = h_in × W_l + (h_in × A) × B + b_l
先算h_in × A,再乘B,这样避免了d_in × d_out的中间矩阵张成和相加,在PyTorch里可以用两次linear完成,速度明显更快,内存占用也更小。
5.2 线上推理的batch化处理
线上服务通常是批量推理,一个batch里可能同时有几十上百个请求。APG按照batch维度并行生成参数,天然适配这种模式。需要注意的是batch内样本差异过大的问题:如果同一个batch里的请求来自差异很大的场景(比如一个是首页热推,一个是搜索尾部结果),生成器输出的参数增量方差会很大,可能引发数值不稳定的隐患。
工程上的解法是控制batch内的特征分布近似度,最粗暴也最有效的方式是按场景分流,让同一个场景的请求尽量落在同一个batch。如果batch特别大,可以对g做batch内的聚类后按簇中心生成参数,牺牲一点细粒度换取推理的稳定性。
5.3 效率收益之外的监控指标
上线APG之后,除了常规的CTR、CVR指标,我强烈建议额外监控两项:生成参数增量的均值绝对值,以及增量在不同流量分桶之间的差异度。
第一个指标反映生成器的活跃程度,如果某天开始增量均值突然变小,大概率是特征输入侧出了问题,生成器退化成了近似零输出。第二个指标反映参数自适应的分布合理性,如果不同分桶之间的参数增量差异极大,说明模型在某些流量上可能处于不稳定的参数状态,需要及时排查特征工程层面的问题。
最后再分享一个经验:APG类方法在实际场景里,带来的AUC提升通常不会特别夸张,但它的价值在于与其他优化手段正交。它不会替代好的特征工程,也不会替代attention结构,而是给模型增加了一维"参数随样本变化"的表达空间。尤其是当你觉得特征交叉已经做到位、模型容量也够、但某些细分流量上的指标始终拉不上去的时候,APG这种参数级的自适应思路,很可能是最后一公里的突破口。