计算广告(一)Ad Click Prediction: a View from the Trenches
——工程实践视角下的广告点击率预估
这是谷歌发表于KDD2013的一篇文章,从年份来看,已经有些年头了,至今已经有800多次被引,这篇文章一出,对工业界做广告的互联网公司还是有比较深远的影响,比如我厂的经典深度学习框架abacus,就借鉴了其中很多重要的思想。从这篇论文的标题也能看出它的主旋律:工程实践。顺便提一下,因为是2013年,那会深度学习刚刚在CV上有点苗头,广告领域有条件的大公司基本还是大规模的LR,没条件的可能连模型都没有,整一些大盘后验统计数据也挺好用。今天再来重读一下经典,又去看这篇文章的原因是前面在写推荐系统(五)wide&deep时,因为wide&deep模型的wide侧使用的优化器为FTRL,虽然对FTRL早已有耳闻,但还没真正去看过原始论文,所以决定再去看一看。其实这篇文章也不是最原始的论文,最原始的论文为[1],这篇论文从理论上介绍了FTRL。而《Ad Click Prediction: a View from the Trenches》这篇论文则注重在把理论运用到实践中,因此这篇论文的作者数量高达15+个。这篇论文的motivation为:在线服务的内存是有限的,如何减少模型的体积,即如何能使得模型变得更加稀疏,减少特征数量。 论文原文为:
Because trained models are replicated to many data centers for serving (see Figure 1), we are much more concerned with sparsification at serving time rather than during training.
基于这个motivation,来看看谷歌提供了哪些可借鉴的经验,这篇文章最难能可贵的地方在于不仅介绍了成功的经验,还介绍了失败的经验,这与一般的吹逼学术论文不太一样。
这篇博客的目录大纲遵循论文的section顺序,如下所示:
- FTRL优化算法
- Per-Coordinate Learning Rates
- Probabilistic Feature Inclusion
- Encoding Values with Fewer Bits
- Training Many Similar Models
- A Single Value Structure
- Computing Learning Rates with Counts
- Subsampling Training Data
- 一些失败的方法
一、FTRL优化算法
为了减少特征的数量,增加模型参数的稀疏性,学术界及工业界提出了各种办法,比如L1正则,FOBOS、RDA等方法。谷歌在论文[1]中提出了FTRL算法,FTRL结合了FOBOS(Forwrad-Backward spliting,前向后向切分)和RDA(Regularized Dual Averaging,正则对偶平均,微软于2010年发表)的优势,同时拥有了更高的稀疏性及精度。
通常来说,梯度下降类算法在batch模式下,增加L1正则后,通常可以产出稀疏解,但是在online模式下,因为只过一次样本的原因,每次梯度方向并不是朝着全局最优解方向去的,因此即使使用了L1正则,也很难产生稀疏解。FOBOS通过直接截断梯度在无精度损失的情况下增加了稀疏解,而RDA直接摒弃了梯度下降,对凸函数直接求解,RDA在优化速度和稀疏性方面都要比FOBOS好,但是精度方面却有所下降。关于FOBOS和RDA这两种方法的原理,这里不多赘述了,有兴趣的可以去看一位大佬写的资料:在线最优化求解。
先来看下FOBOS和RDA的公式:
- FOBOS
w t + 1 = arg min w { g t ⋅ w + λ ∥ w ∥ 1 + 1 2 σ 1 : t ∥ w − w t ∥ 2 2 } (1) w_{t+1} = \argmin_{w}\{g_t \cdot w + \lambda\left\|w\right\|_1 + \frac{1}{2}\sigma_{1:t}\left\|w-w_t \right\|_2^2\} \tag{1} wt+1=wargmin{gt⋅w+λ∥w∥1+21σ1:t∥w−wt∥22}(1) - RDA
w t + 1 = arg min w { g 1 : t ⋅ w + t λ ∥ w ∥ 1 + 1 2 σ 1 : t ∥ w − 0 ∥ 2 2 } (2) w_{t+1} = \argmin_{w}\{g_{1:t} \cdot w + t\lambda\left\|w\right\|_1 + \frac{1}{2}\sigma_{1:t}\left\|w-0 \right\|_2^2\} \tag{2} wt+1=wargmin{g1:t⋅w+tλ∥w∥1+21σ1:t∥w−0∥22}(2)
比较公式1和公式2能够发现,L1-FOBOS和L1-RDA的区别为(摘自在线最优化求解。):
- FOBOS计算的是累加梯度及L1正则项只考虑当前模的贡献,而RDA采用了累加的方式
- FOBOS第三项限制 w w w的变化不能离上一次迭代过的解太远,而后者则限制 w w w不能离0点太远。
而FTRL综合考虑了FOBOS和RDA对于正则项和
w
w
w限制的区别,其公式为:
w
t
+
1
=
arg min
w
{
g
1
:
t
⋅
w
+
λ
1
∥
w
∥
1
+
λ
2
1
2
∥
w
∥
2
2
+
1
2
∑
s
=
1
t
σ
s
∥
w
−
w
s
∥
2
2
}
(3)
w_{t+1} = \argmin_{w}\{g_{1:t} \cdot w + \lambda_1\left\|w\right\|_1 + \lambda_2\frac{1}{2}\left\|w \right\|_2^2 + \frac{1}{2}\sum_{s=1}^t\sigma_s\left\|w-w_s \right\|_2^2\} \tag{3}
wt+1=wargmin{g1:t⋅w+λ1∥w∥1+λ221∥w∥22+21s=1∑tσs∥w−ws∥22}(3)
公式3看起来很复杂,想要实现更新权重也比较困难,谷歌这篇论文对其重写了下(把公式3中最后一项展开):
w
t
+
1
=
arg min
w
{
(
g
1
:
t
−
∑
s
=
1
t
)
⋅
w
+
λ
1
∥
w
∥
1
+
1
2
(
λ
2
+
∑
s
=
1
t
σ
s
)
∥
w
∥
2
2
+
1
2
∑
s
=
1
t
σ
s
∥
w
s
∥
2
2
}
=
arg min
w
{
z
t
⋅
w
+
λ
1
∥
w
∥
1
+
1
2
(
λ
2
+
∑
s
=
1
t
σ
s
)
∥
w
∥
2
2
}
(4)
\begin{aligned} w_{t+1} &= \argmin_{w}\{(g_{1:t}-\sum_{s=1}^t )\cdot w + \lambda_1\left\|w\right\|_1 + \frac{1}{2}(\lambda_2 + \sum_{s=1}^t\sigma_s)\left\|w \right\|_2^2 + \frac{1}{2}\sum_{s=1}^t\sigma_s\left\|w_s \right\|_2^2\} \\ \tag{4} &=\argmin_w\{z_{t} \cdot w + \lambda_1\left\|w\right\|_1 + \frac{1}{2}(\lambda_2 + \sum_{s=1}^t\sigma_s)\left\|w \right\|_2^2\} \end{aligned}
wt+1=wargmin{(g1:t−s=1∑t)⋅w+λ1∥w∥1+21(λ2+s=1∑tσs)∥w∥22+21s=1∑tσs∥ws∥22}=wargmin{zt⋅w+λ1∥w∥1+21(λ2+s=1∑tσs)∥w∥22}(4)
上面公式4从第一步到第二步的推导为:
1
2
∑
s
=
1
t
σ
s
∥
w
s
∥
2
2
\frac{1}{2}\sum_{s=1}^t\sigma_s\left\|w_s \right\|_2^2
21∑s=1tσs∥ws∥22 这一项相比较
w
w
w其实就是个常数,所以直接扔掉对求导没影响。另外,另
z
t
=
g
1
:
t
−
1
2
∑
s
=
1
t
σ
s
w
s
z_t =g_{1:t}- \frac{1}{2}\sum_{s=1}^t\sigma_sw_s
zt=g1:t−21∑s=1tσsws。另外根据论文中的介绍,
针对各个特征拆解成
N
N
N个独立的最小化问题:
min
w
i
∈
R
{
z
t
,
i
⋅
w
+
λ
1
∥
w
∥
1
+
1
2
(
λ
2
+
∑
s
=
1
t
σ
s
)
∥
w
∥
2
2
}
(5)
\min_{w_i \in R}\{z_{t,i} \cdot w + \lambda_1\left\|w\right\|_1 + \frac{1}{2}(\lambda_2 + \sum_{s=1}^t\sigma_s)\left\|w \right\|_2^2\} \tag{5}
wi∈Rmin{zt,i⋅w+λ1∥w∥1+21(λ2+s=1∑tσs)∥w∥22}(5)
因此,上式是一个典型的无约束最优化问题,直接一波求导并另导数等于0,我们可得下面的式子:
w
t
+
1
,
i
=
{
0
i
f
∣
z
t
,
i
∣
<
λ
1
−
(
λ
2
+
∑
s
=
1
t
σ
s
)
−
1
(
z
t
,
i
−
λ
1
s
g
n
(
z
t
,
i
)
)
o
t
h
e
r
w
i
s
e
(6)
w_{t+1,i}=\left\{\begin{matrix} \tag{6} 0 & if \left| z_{t,i} \right| < \lambda_1\\ -(\lambda_2 + \sum_{s=1}^t\sigma_s)^{-1}(z_{t,i} - \lambda_1sgn(z_{t,i})) & otherwise\\ \end{matrix}\right.
wt+1,i={0−(λ2+∑s=1tσs)−1(zt,i−λ1sgn(zt,i))if∣zt,i∣<λ1otherwise(6)
考虑到《二、Per-Coordinate Learning Rates》中公式x,设
σ
1
:
t
=
1
n
t
\sigma_{1:t} = \frac{1}{n_t}
σ1:t=nt1,所以公式6中,
∑
s
=
1
t
σ
s
=
1
n
t
,
i
=
(
β
+
(
∑
s
=
1
t
g
s
,
i
2
)
α
)
\sum_{s=1}^t\sigma_s= \frac{1}{n_{t,i}}=(\beta+\frac{\sqrt(\sum_{s=1}^tg_{s,i}^2)}{\alpha})
∑s=1tσs=nt,i1=(β+α(∑s=1tgs,i2))。
最后论文中也给出了伪代码:
paddlepaddle框架中也提供了该算法,参见文档:FtrlOptimizer
二、Per-Coordinate Learning Rates
在这这篇论文之前(2013年)通常优化器的学习率设置基本都是简单粗暴的设置一个全局固定的值,更高级一点的就是随着迭代加深,学习率做个衰减,比如:
n
t
=
1
(
t
)
n_t=\frac{1}{\sqrt(t)}
nt=(t)1。当然现在已经发展出了非常多的自适应学习率的优化算法,具体的可参见我的博客:深度学习中优化方法——momentum、Nesterov Momentum、AdaGrad、Adadelta、RMSprop、Adam。谷歌这篇论文提出了Per-Coordinate Learning Rates,其实就是对每个特征都有一个学习率,这个想法是很meaningful的,因为在真实场景里的样本中,每个特征出现的次数显然是不一样的,对于出现次数比较多的特征,说明它已经训练的比较充分了,学习到的参数已经比较置信了,这时候其实学习率就可以设置的比较小,防止跳出这种最优解。而对于出现次数比较少的特征,说明训练的不是很充分,参数也不置信,那么就需要比较大的学习率,尽快的寻找最优解。
直接来看谷歌这篇论文给出的公式吧:
n
t
,
i
=
α
β
+
∑
s
=
1
t
g
s
,
i
2
(7)
n_{t,i} = \frac{\alpha}{\beta + \sqrt{\sum_{s=1}^tg_{s,i}^2}} \tag{7}
nt,i=β+∑s=1tgs,i2α(7)
其中,
g
s
,
i
g_{s,i}
gs,i表示第
s
s
s轮迭代时第
i
i
i个特征的梯度;
n
t
,
i
n_{t,i}
nt,i表示第t轮迭代时第
i
i
i个特征的学习率;
α
\alpha
α和
β
\beta
β为两个超参数,
β
=
1
\beta=1
β=1是一个比较好的取值,
α
\alpha
α则需要根据具体的数据集和特征来决定。
从上面这个公式能够看出,第
t
t
t轮迭代时第
i
i
i个特征的学习率由第
i
i
i个特征到目前第
t
t
t为止所累积的梯度平方之和。所以,如果前面累积的梯度和越大,学习率则越小。
论文实验发现,相比家全局学习率,Per-Coordinate Learning Rates在AucLoss上下降了11.2%,在广告领域,如果能下降1%都是个很大的改进了。在我们自己平时业务中,发现使用Per-Coordinate Learning Rates在ACU上也有百分位的提升。
细心的同学能够发现,如果要实现这个公式,需要存储从第1次迭代到第 t t t次的梯度,这无疑浪费存储(其实我个人感觉也还好),谷歌这篇文章给出了一个替代方法,比较节省内存,具体的方法我放到了 《七、Computing Learning Rates with Counts》里讲,大家可以直接跳到这一节。
下面【3-8】的方法都是为了节省内存。
三、Probabilistic Feature Inclusion
在大规模广告/推荐数据中,有很多特征出现的次数都很少,比如就出现1,2次,这些特征对于训练模型实际上是没啥帮助的,但反而这部分特征增加了模型体积,占用存储空间。因此,想要降低模型的尺寸,则需要减少模型中特征的数目。通常,我们离线时可以做个全局的统计,对于出现次数少于
k
k
k次的特征取值直接删掉。但是对于online learning这种方法显然行不通,因此需要别的方法。谷歌这里使用了概率方法,即当遇到一个新的特征取值时,以一定的概率来动态决定某一个特征是否需要加入到模型中,论文给出了两种方法:
- Poisson Inclusion:当遇到一个特征值时,如果它没在模型中出现过,那么以概率 p p p将其加入到模型中。所以,出现次数较多的特征值,则有更大概率被加到模型中。
- Bloom Filter Inclusion:使用布隆过滤器的方法,当一个特征值出现次数超过 n n n次时,则加入到模型中。
实验表明(如下表),通过布隆过滤器调优之后,模型的 AUC 仅仅降低了 0.008%,但是内存的消耗却减少了 66% ,这是一个非常恐怖的数字。
四、Encoding Values with Fewer Bits
论文认为使用32位或者64位浮点数有点奢侈,谷歌这波人通过分析发现,在他们的场景下,模型大部分的参数的区间是在[-2,2]中的(这个地方不得不服,实践出真知,一切脱离业务场景的东西都是扯淡)。所以,为了节省内存,使用q2.13的encode方法。这个方法根据论文中的介绍是:1bit用来表示正负, 2bit用来表示整数部分,13bit用来表示小数部分,一共16bit。
实验表明,相对于64bit浮点数来说,使用q2.13浮点编码方法节省了75%的内存,而AucLoss几乎没有损失。
这个方法可能很难直接用到自己的业务场景下,但至少提供了一种压缩数据的思路。
五、Training Many Similar Models
这一块,在常见的业务中,我没怎么见过有这么操作的,所以这一块暂时不讲了。
六、A Single Value Structure
这一块和五一样,也不讲了,有兴趣的,或者等到需要用到的时候,再补充。
七、Computing Learning Rates with Counts
这里是和《二、Per-Coordinate Learning Rates》联系在一起的,目的是为了解决二中需要存储每个特征历史梯度浪费内存的问题,建议和第二节一起看。在这一节中,谷歌团队证明了不必存储历史梯度,只需要统计特征
i
i
i出现时正样本和负样本的个数即可。 假设特征
i
i
i出现时正样本和负样本的个数分别为
P
P
P和
N
N
N,则CTR,
p
=
P
N
+
P
p=\frac{P}{N+P}
p=N+PP,下面是公式证明:
∑
g
t
,
i
2
=
∑
p
o
s
i
t
i
v
e
e
v
e
n
t
s
(
1
−
p
t
)
2
+
∑
n
e
g
a
t
i
v
e
e
v
e
n
t
s
p
t
2
≈
P
(
1
−
P
N
+
P
)
2
+
N
(
P
N
+
P
)
2
=
P
N
N
+
P
(8)
\begin{aligned} \sum{g_{t,i}^2} &= \sum_{positive \ events}{(1-p_t)}^2 + \sum_{negative \ events}{p_t}^2 \\ &\approx P(1-\frac{P}{N+P})^2 + N(\frac{P}{N+P})^2 \\ \tag{8} &=\frac{PN}{N+P} \end {aligned}
∑gt,i2=positive events∑(1−pt)2+negative events∑pt2≈P(1−N+PP)2+N(N+PP)2=N+PPN(8)
八、Subsampling Training Data
现在,对多数类样本进行下采样也是比较常见的了。例如在广告/推荐领域通常负样本(曝光未点击样本)的数量要远远大于正样本数量(点击样本),如果地主家机器资源充足,不在乎钱的话,并且能忍受长的训练时间,自然是用全部的样本进行训练得到的模型效果是最好的。但现在问题是:1. 长的训练时间无法忍受;2. 在动不动每天几亿甚至十几亿样本的情况下,但训练机器资源紧张。所以即使有一点点精读损失的情况下,如果能够大幅减少样本数量那也是很划算的。
在广告领域,通常来说,信息流广告的点击率也就2%-5%左右,开屏广告的点击率在10%-20%的样子,所以负样本数量远远大于正样本数量。这种情况下,显然正样本对我们是更有价值的,而负样本则可以采样减少数量,通常的做法基本都是:正样本全部保留,负样本以概率
r
r
r进行随机采样。采样必然导致样本分布发生变化,所以一般都会对损失函数做一个修改,对采样的样本乘以一个权重
w
w
w。这个权重会在bp时作用到梯度上。
这篇论文还从理论上证明了上面这种做法并没有改变原数据(采样前)的
l
o
s
s
loss
loss损失,我这里写下证明过程:
- 假设正样本不做任何采样,负样本以概率 r r r进行随机采样。
- 则正样本权重为1,采样得到的这部分负样本每个样本权重为 w t = 1 r w_t = \frac{1}{r} wt=r1
- 令 s t s_t st为每个样本被采样的概率,则显然 s t s_t st为1(正样本时)或者 r r r (负样本时)
- 对于LR模型的交叉熵损失函数: l t ( w t ) = − y t l o g p t − ( l − y t ) l o g ( 1 − p t ) l_t(w_t)=-y_tlogp_t - (l-y_t)log(1 - p_t) lt(wt)=−ytlogpt−(l−yt)log(1−pt),其中 p t = δ ( w t ⋅ x t ) p_t=\delta(w_t \cdot x_t) pt=δ(wt⋅xt)为样本的预测概率,则采样后的样本损失函数为: E [ l t ( w t ) ] = s t w t l t ( w t ) + ( 1 − s t ) 0 = s t 1 s t l t ( w t ) = l t ( w t ) E[l_t(w_t)] = s_tw_tl_t(w_t) + (1-s_t)0 = s_t\frac{1}{s_t}l_t(w_t) = l_t(w_t) E[lt(wt)]=stwtlt(wt)+(1−st)0=stst1lt(wt)=lt(wt)。这个推导过程稍微解释下,期望=被采样到的样本的损失+没被采样到的样本损失(被丢到的这部分样本损失自然为0)。能够看出与采样前的损失是等价的。
谷歌这篇论文也提到,他们通过实验验证这种方法是可行的,对模型精度影响很小。
九、一些失败的方法
这也是这篇论文的独到之处,不仅介绍了有用的trick,同样把一些尝试了但没效果的trick记录下来了,供后人参考。
论文里列举出的一些unsuccessful的方法如下:
- feature hash
- dropout
- feature bagging
- feature vector normalization
参考文献
[1]: McMahan B. Follow-the-regularized-leader and mirror descent: Equivalence theorems and l1 regularization[C] // Proceedings of the Fourteenth International Conference on Artificial Intelligence and Statistics. JMLR Workshop and Conference Proceedings, 2011: 525-533.
[2]: McMahan H B, Holt G, Sculley D, et al. Ad click prediction: a view from the trenches[C] // Proceedings of the 19th ACM SIGKDD international conference on Knowledge discovery and data mining. 2013: 1222-1230.
[3]: 冯扬,在线最优化求解