蘑菇街增量学习番外篇三:deepFM 的动态正则实践

蘑菇街增量学习番外篇三:deepFM 的动态正则实践

作者:美丽联合集团 算法工程师 琦琦
公众号关注:诗品算法
本文经作者授权转载,转载请联系原作者

历史系列文章

蘑菇街增量学习番外篇二:优化器设计理论篇(AdamW、AdaDeltaW、FTRL)
蘑菇街增量学习番外篇一:动态正则之 tensorflow 中 div 转 mod 设计(含代码实现)
文本相关性在蘑菇街搜索推荐排序系统中的应用
蘑菇街首页推荐视频流——增量学习与 wide&deepFM 实践(工程 + 算法)
蘑菇街首页推荐多目标优化之 reweight 实践:一把双刃剑?

0、引言

这篇文章仍是在蘑菇街 增量学习背景下的实践,增量学习的理论很简单,但实践起来,还是有很多细节和 trick 的。比如,针对不同的模型结构,我们可以设计不同的优化器承接,其对应的动态正则设计方案也会有所差异。

这篇文章是已受理的一篇专利中的核心内容。

对于增量学习整体框架尚不了解的童鞋,看这里:
蘑菇街首页推荐视频流——增量学习与 wide&deepFM 实践(工程 + 算法)

我们在 FM 和 deep 模型训练中,使用了 AdaDelta + 动态 weight decay/FTRL + 动态 L1 正则优化器;在 wide 模型训练中,使用了 FTRL + 动态 L1 正则优化器。*

以下内容以 AdaDelta + 动态 weight decay 为主,其余自适应学习率优化器的原理类似。FTRL 优化器自带 L1/L2 正则,因此只需实现动态 L1 正则。

1、何为 weight decay?

深度学习中的绝大多数目标函数都很复杂,且通常都是高维的。很多优化问题不存在解析解,只能通过基于数值方法的优化算法找到近似解,即数值解。优化在深度学习中的主要挑战是局部最小值和鞍点,其中鞍点更为常见。梯度下降算法中的超参学习率(lr)通常需要人工设定。如果 lr 过小,会导致参数更新缓慢,需要经过很多次的迭代才能收敛;如果 lr 过大,参数可能会越过最优解并逐渐发散。因此出现了一大批优秀的自适应学习率算法,如 AdaGrad、RMSProp、AdaDelta、Adam 等。

  1. 上面列出的每一种优化算法,虽然可以自动调整学习率,但是对于正则功能却捉襟见肘。比如,若目标函数有关自变量中某元素的偏导数一直较大(或者说变化很快),那么该元素的学习率将下降较快(分母是偏导数平方的累积),同时也会带来正则系数的快速下降。这意味着什么呢?梯度快速变化参数的正则力度小于那些梯度缓慢变化参数的正则力度。这显然不符合我们对于正则功能的认知。 因此 L2 正则在这些自适应学习率算法中,并不是那么有效,因为正则系数被“平均”了。具体原因和细节见:
    蘑菇街增量学习番外篇二:优化器设计理论篇(AdamW、AdaDeltaW、FTRL)
  2. 之后出现了 weight decay 的方式,用来替代 L2 regularization,使学习率与 weight decay 实现解耦。多数使用 L2 正则的自适应优化器,效果往往不如 SGD,因此 AdamW(Adam + weight decay)绝对是一个优化技术上的变革。

我们在对 deep 进行优化时,尝试过 Adam/AdaDelta/FTRL 等优化器,并在这些优化器的基础上实现了动态 weight decay/动态 L1 正则。

2、何为动态正则/动态 weight decay?

固定阈值的特征频次过滤 & 固定正则

我们知道,L2 正则化的效果是对原最优解的每个元素进行不同比例的放缩;L1 正则化则会使原最优解的元素产生不同量的偏移,并使某些元素为 0,从而产生稀疏性。 在经典的优化器实现中,L1/L2 正则对所有特征都是一视同仁的。但若每个参数的正则力度都是一致的,则会在一定程度上影响模型性能。在样本构建时,我们一般使用固定阈值过滤低频特征。在我们实现的增量框架下,之前在全量样本构建过程中被全量阈值过滤的特征,其频次会被保留,等到下一次增量到来时,若全量中被过滤的这些特征再次出现,则会将全量 + 当前增量的频次作为该特征的频次。这种对特征出现频次进行计数,只有达到一定阈值后再进入训练的设计方案会破坏样本完整性,如全量频次 99,增量频次 1,阈值过滤 100,则该特征出现的前 99 次都被忽略,只会训练该特征出现的一次,导致模型训练结果的稳定性差。

动态 weight decay 设计

针对自适应学习率优化算法,我们使用 weight decay 替代 L2 正则,并使 weight decay 参数随特征出现频次而动态变化,从而动态调整不同频次特征的正则力度。以 AdaDelta 为例,相当于我们对传统 AdaDelta 优化器进行了两点优化,第一点:使用 weight decay 替代 L2 正则;第二点:对于不同的特征来说,根据其出现的频次,设置不同的动态正则系数。

特征频次和参数估计的置信度相关,特征在本次训练样本中出现的频次越低,置信度也越低。因此我们在纯频率统计的基础上增加一个先验分布(正则项),当频率统计置信度越低的时候,越倾向于先验分布,相应的正则系数会更大。

3、AdaDelta + 动态 weight decay

算法流程

以首页推荐视频流为例,以历史推荐的曝光样本对应的视频 ID 以及用户历史点击序列中的视频 ID 构建 deep 字典集合。字典中的每一个视频 id 都是一维特征,用于后续的 deep 模型训练。

统计出在当前样本中,每个视频 id 出现的总频次,计作 图片 。AdaDelta 优化器以权重衰减参数替代正则化项,且在特征门限范围内,权重衰减参数与特征样本中特征出现的频次呈负相关。

动态 weight decay 公式,利用了指数加权平均思路,同时考虑当前频次和历史频次总和的影响,使这两个变量同时影响增量模型训练。其中,历史频次在公式中的系数 图片 较小,在 图片 时刻的特征频次计算公式如下:



图片

使用 AdaDelta 优化器进行动态 weight decay 的实现步骤如下:

  1. 计算梯度 图片
  2. 利用权重衰减参数,根据公式:

图片

图片 更新参数;

  1. 根据梯度 图片 ,依据公式 图片 更新参数。

其中, 图片 表示利用权重衰减系数对参数 图片 进行更新得到的参数; 图片
表示利用梯度对参数 图片 进行更新得到的参数。 图片 表示每一个特征对应的权重衰减(weight decay)参数, 图片 表示惩罚倍数(参考值 1.5), 图片 表示特征门限(参考值 1000), 图片 是当前特征在当前训练样本中的真实出现频次(也可以使用上述指数加权平均的结果替代),从增量样本构建提供给增量训练的 hdfs 文件里取。

小疑问——为什么先实施 weight decay?再实施梯度下降?

我们来看看源码实现:


首先计算 weight decay:_decay_weights_op(),再实施梯度下降: _apply_dense()。

理论上来说,无论是先进行梯度下降再计算 weight decay,还是先计算 weight decay 再进行梯度下降,对最终结果的影响都不大。无非是对当前参数实施衰减还是对上一步的参数实施衰减的区别。
AdaDelta 调参小坑

理论上,AdaDelta 是没有 lr 参数的,但是 AdaDelta 的 tf 源码实现函数中有 lr 形参,且其参数值默认为 0.01。实践中最好将其设置为 1.0,否则 AdaDelta 的效果不会很好。在 tensorflow 的 Google 源码中(如下),lr 参数是用来乘以更新后的梯度的。

4、AdaDelta + 动态 weight decay 代码实现

首先,if_dynamic_regular 是一个布尔变量,用来判断是否需要在优化器中加入动态 weight decay 功能:

log.info("weight_decay:{}, learning_rate:{}, rho:{}, epsilon:{}".format(wd, lr, rho, epsilon))
if if_dynamic_regular:
    opt = adaDelta_dynamic_optimizer(emb_matrix, feat_regular_param, wd, lr, rho, epsilon)
else:
    adadeltaWOptimizer = tf.contrib.opt.extend_with_decoupled_weight_decay(tf.train.AdadeltaOptimizer)
    opt = adadeltaWOptimizer(weight_decay=wd, learning_rate=lr, rho=rho, epsilon=epsilon)

如果选择 weight decay 功能,核心代码如下:

def adaDelta_dynamic_optimizer(emb_matrix, feat_regular_param, wd, lr, rho, epsilon):
    dynamic_weight_decay_dict = dict()
    for dict_name in feat_regular_param:
        feat_regular_param[dict_name] = [float(wd * (1 + inc)) for inc in feat_regular_param[dict_name]]
        feat_regular_param[dict_name] = np.array(feat_regular_param[dict_name]).reshape(np.size(feat_regular_param[dict_name]), -1)
        dynamic_weight_decay_dict[emb_matrix[dict_name]] = feat_regular_param[dict_name]
    adaDeltaWOptimizer = tf.contrib.opt.extend_with_decoupled_dynamic_weight_decay(tf.train.AdadeltaOptimizer)
    opt = adaDeltaWOptimizer(weight_decay_dict=dynamic_weight_decay_dict, learning_rate=lr, rho=rho, epsilon=epsilon)
    return opt

其中,emb_matrix 表示特征权重矩阵,在 wide 中,size 为 feature_size * 1,在 deep 中,size 为 feature_size * embedding_size。feat_regular_param 与 emb_matrix 一一对应,表示 weight decay/L1 正则参数矩阵。

由于 emb_matrix 在 tensorflow 中是按照 mod 方式进行 embedding_lookup 的,因此 feat_regular_param 对应的 weight decay 或 L1 正则参数矩阵也需要按照 mod 方式重新排序,保证传给优化器的特征权重矩阵和 weight decay/L1 正则参数矩阵是一一对应的。

这个矩阵 div 转 mod 的设计原则和注意事项见下面的文章:

琦琦:蘑菇街增量学习番外篇一:动态正则之 tensorflow 中 div 转 mod 设计(含代码实现

至此,我们就将蘑菇街增量学习实践中涉及到的相关知识点全部串联起来了。撒花 ~~~


原创不易,大家多多关注转发点赞收藏!笔芯 ~

参考:

  1. https://www.tensorflow.org/addons/api_docs/python/tfa/optimizers/extend_with_decoupled_weight_decay
  2. https://github.com/tensorflow/addons/blob/v0.10.0/tensorflow_addons/optimizers/weight_decay_optimizers.py#L184-L269

本文地址:https://www.6aiq.com/article/1608409617918
本文版权归作者和AIQ共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出