FM, FTRL, Softmax

FM, FTRL, Softmax

前言

  最近公司内部举办了一届数据挖掘大赛,题目是根据用户的一些属性和行为数据来预测性别和年龄区间,属于一个二分类问题(性别预测男女)和一个多分类问题(年龄分为7个区间),评判标准为logloss。共有五六十支队伍提交,我们组的三名小伙伴最终取得第三名的好成绩,跟前两名只有千分之一二的差距。

  赛后总结,发现前6名全部使用了DNN模型,而我们团队比较特别的是,不只使用了DNN,还有FM,最终方案是六七个DNN模型和一个FM模型的ensembling。

  其实比赛刚开始,他们使用的是XGBoost,因为XGBoost的名头实在太响。但这次比赛的数据量规模较大,训练样本数达到千万,XGBoost跑起来异常的慢,一个模型要跑一两天。于是我把几个月前写的FM工具给他们用,效果非常好,二分类只需十几分钟,多分类也就半个多小时,logloss和XGBoost基本持平,甚至更低。最终他们抛弃了XGBoost,使用FM在快速验证特征和模型融合方面都起到了很好的作用。此外,我们组另外两名实习生仅使用此FM工具就取得了第七名的成绩。

  最初写此FM代码时正值alphaGo完虐人类,因此随手给这个工具起了个名字叫alphaFM,今天我就来分享一下这个工具是如何实现的。

alphaFM介绍

  代码地址在:
  https://github.com/CastellanZhang/alphaFM
  https://github.com/CastellanZhang/alphaFM_softmax

  alphaFM是Factorization Machines的一个单机多线程版本实现,用于解决二分类问题,比如CTR预估,优化算法采用了FTRL。我其实把sgd和adagrad的方法也实现了,但最终发现还是FTRL的效果最好。

  实现alphaFM的初衷是解决大规模数据的FM训练,在我们真实的业务数据中,训练样本数常常是千万到亿级别,特征维度是百万到千万级别甚至上亿,这样规模的数据完全加载到内存训练已经不太现实,甚至下载到本地硬盘都很困难,一般都是经过spark生成样本直接存储在hdfs上。

  alphaFM用于解决这样的问题特别适合,一边从hdfs下载,一边计算,一个典型的使用方法是这样:

  训练:10个线程计算,factorization的维度是8,最后得到模型文件fm_model.txt

  hadoop fs -cat train_data_hdfs_path | ./fm_train -core 10 -dim 1,1,8 -m fm_model.txt

  测试:10个线程计算,factorization的维度是8,加载模型文件fm_model.txt,最后输出预测结果文件fm_pre.txt

  hadoop fs -cat test_data_hdfs_path | ./fm_predict -core 10 -dim 8 -m fm_model.txt -out fm_pre.txt

  当然,如果样本文件不大,也可以先下载到本地,然后再运行alphaFM。

  由于采用了FTRL,调好参数后,训练样本只需过一遍即可收敛,无需多次迭代,因此alphaFM读取训练样本采用了管道的方式,这样的好处除了节省内存,还可以通过管道对输入数据做各种中间过程的转换,比如采样、格式变换等,无需重新生成训练样本,方便灵活做实验。

  alphaFM还支持加载上次的模型,继续在新数据上训练,理论上可以一直这样增量式进行下去。

  FTRL的好处之一是可以得到稀疏解,在LR上非常有效,但对于FM,模型参数v是个向量,对于每一个特征,必须w为0且v的每一维都为0才算稀疏解, 但这通常很难满足,所以加了一个force_v_sparse的参数,在训练过程中,每当w变成0时,就强制将对应的v变成0向量。这样就可以得到很好的稀疏效果,且在我的实验中发现最终对test样本的logloss没有什么影响。

  当将dim参数设置为1,1,0时,alphaFM就退化成标准的LR的FTRL训练工具。不禁想起我们最早的LR的FTRL代码还是勇保同学写的,我现在的代码基本上还是沿用了当初的多线程思路,感慨一下。

  alphaFM能够处理的特征维度取决于内存大小,训练样本基本不占内存,理论上可以处理任意多的数量。后续可以考虑基于ps框架把alphaFM改造成分布式版本,这样就可以支持更大的特征维度。

  alphaFM_softmax是alphaFM的多分类版本。两个工具的具体使用方法和参数说明见代码的readme,这里不再详述。

  接下来请各位打起精神,我们来推一推公式。诗云,万丈高楼平地起,牛不牛逼靠地基。公式就是算法工具的地基,公式整明白了,像我们这种”精通”C++的(谁简历里不是呢:-P),实现就是分分钟的事(装B中,勿扰:-)。

二分类问题

  对于二分类,最常见的模型是LR,搭配FTRL优化算法。LR的输出会用到sigmoid函数,定义为:

σ(x)=11+ex σ(x)=11+e−x

  LR预测输入 x x是正样本的概率:
P(y=1|x,w)=11+ewTx=σ(wTx) P(y=1|x,w)=11+e−wTx=σ(wTx)

  可以看到, σ σ函数的参数部分  wTx wTx 是一个线性函数,这也就是LR被称作线性模型的原因,模型参数只有一个 w w向量,相对简单。如果我们把这部分弄复杂呢?比如这样:
y^(x|Θ):=w0+i=1nwixi+i=1nj=i+1nvi,vjxixj=w0+i=1nwixi+i=1nj=i+1nxixjf=1kvi,fvj,f=w0+i=1nwixi+12f=1k(i=1nvi,fxi)2i=1nv2i,fx2i y^(x|Θ):=w0+∑i=1nwixi+∑i=1n∑j=i+1n⟨vi,vj⟩xixj=w0+∑i=1nwixi+∑i=1n∑j=i+1nxixj∑f=1kvi,fvj,f=w0+∑i=1nwixi+12∑f=1k((∑i=1nvi,fxi)2−∑i=1nvi,f2xi2)

  其中, xRn x∈Rn w0R w0∈R wRn w∈Rn VRn×k V∈Rn×k,这其实就是一个2阶FM,模型参数  Θ={w0,w1,,wn,v1,1,,vn,k} Θ={w0,w1,…,wn,v1,1,…,vn,k} 。如果直接将  y^(x|Θ) y^(x|Θ) 做输出,采用平方损失函数便可解决回归问题。而对于二分类问题,外面套一个sigmoid函数即可:
P(y=1|x,Θ)=11+ey^(x|Θ) P(y=1|x,Θ)=11+e−y^(x|Θ)

  对于 y{1,1} y∈{−1,1},可统一成形式:
P(y|x,Θ)=11+eyy^(x|Θ)=σ(yy^(x|Θ)) P(y|x,Θ)=11+e−yy^(x|Θ)=σ(yy^(x|Θ))

  模型参数估计采用最大似然的方法,对于训练数据 S S,最优化问题为:
argmaxΘ(x,y)SP(y|x,Θ)=argminΘ(x,y)SlnP(y|x,Θ) arg⁡maxΘ⁡∏(x,y)∈SP(y|x,Θ)=arg⁡minΘ⁡∑(x,y)∈S−ln⁡P(y|x,Θ)

  即样本  (x,y) (x,y) 的损失函数为:
l(Θ|x,y)=lnP(y|x,Θ)=lnσ(yy^(x|Θ)) l(Θ|x,y)=−ln⁡P(y|x,Θ)=−ln⁡σ(yy^(x|Θ))

  此损失函数对  y^ y^ 求偏导会得到一个优雅简单的形式:
ly^=y(σ(yy^)1) ∂l∂y^=y(σ(yy^)−1)

  再配合上  y^ y^ 对模型参数的偏导:
y^θ=1,xi,xinj=1vj,fxjvi,fx2iifθisw0ifθiswiifθisvi,f ∂y^∂θ={1,ifθisw0xi,ifθiswixi∑j=1nvj,fxj−vi,fxi2ifθisvi,f

  便可得到损失函数  l l 对所有模型参数的偏导,即:
gw0=lw0=y(σ(yy^)1)gwi=lwi=y(σ(yy^)1)xigvi,f=lvi,f=y(σ(yy^)1)(xij=1nvj,fxjvi,fx2i) g0w=∂l∂w0=y(σ(yy^)−1)giw=∂l∂wi=y(σ(yy^)−1)xigi,fv=∂l∂vi,f=y(σ(yy^)−1)(xi∑j=1nvj,fxj−vi,fxi2)

此时,我们能够很自然的想到用SGD的方法来求解模型参数,但我这里采用了更加高效的FTRL优化算法。

  让我们来简单回顾一下FTRL,Google在2013年放出这个优化方法,迅速火遍大江南北,原始论文里只是用来解决LR问题,论文截图如下:

  但其实FTRL是一个online learning的框架,能解决的问题绝不仅仅是LR,已经成了一个通用的优化算子,比如TensorFlow的optimizer中都包含了FTRL。我们只要把截图中的伪代码修改, pt pt的计算改为  y^(x|Θ) y^(x|Θ),对于每一轮的特征向量 x x的每一维非0特征 xi xi,都要相应的更新模型参数 w0,wi,vi,1,,vi,k w0,wi,vi,1,…,vi,k,更新公式不变和截图一致,梯度 g g的计算即为损失函数对每个参数的偏导,前面已经给出。 σ,z,n σ,z,n的更新公式不变。伪代码如下:


Algorithm: alphaFM


Input:paramtersαw,αv,βw,βv,λw1,λv1,λw2,λv2,σ Input:paramtersαw,αv,βw,βv,λ1w,λ1v,λ2w,λ2v,σ
Init:w0=0;nw0=0;zw0=0; Init:w0=0;n0w=0;z0w=0;
Init:i,f,wi=0;nwi=0;zwi=0;vi,fN(0,σ);nvi,f=0;zvi,f=0; Init:∀i,∀f,wi=0;niw=0;ziw=0;vi,f∼N(0,σ);ni,fv=0;zi,fv=0;
fort=1toT,do fort=1toT,do
ReceivefeaturevectorxandletI={i|xi0} ReceivefeaturevectorxandletI={i|xi≠0}

w0=0(βw+nw0αw+λw2)1(zw0sgn(zw0)λw1)if|zw0|λw1otherwise. w0={0if|z0w|≤λ1w−(βw+n0wαw+λ2w)−1(z0w−sgn(z0w)λ1w)otherwise.

foriI,compute fori∈I,compute
wi=0(βw+nwiαw+λw2)1(zwisgn(zwi)λw1)if|zwi|λw1otherwise. wi={0if|ziw|≤λ1w−(βw+niwαw+λ2w)−1(ziw−sgn(ziw)λ1w)otherwise.

forf=1tok,compute forf=1tok,compute
vi,f=0(βv+nvi,fαv+λv2)1(zvi,fsgn(zvi,f)λv1)if|zvi,f|λv1otherwise. vi,f={0if|zi,fv|≤λ1v−(βv+ni,fvαv+λ2v)−1(zi,fv−sgn(zi,fv)λ1v)otherwise.

endfor endfor
endfor endfor
Computey^(x|Θ) Computey^(x|Θ)
Observelabely{1,1} Observelabely∈{−1,1}
computegw0 computeg0w
σw0=1αw(nw0+(gw0)2nw0) σ0w=1αw(n0w+(g0w)2−n0w)
zw0zw0+gw0σw0w0 z0w←z0w+g0w−σ0ww0
nw0nw0+(gw0)2 n0w←n0w+(g0w)2
foriI,do fori∈I,do
computegwi computegiw
σwi=1αw(nwi+(gwi)2nwi) σiw=1αw(niw+(giw)2−niw)
zwizwi+gwiσwiwi ziw←ziw+giw−σiwwi
nwinwi+(gwi)2 niw←niw+(giw)2
forf=1tok,do forf=1tok,do
computegvi,f computegi,fv
σvi,f=1αv(nvi,f+(gvi,f)2nvi,f) σi,fv=1αv(ni,fv+(gi,fv)2−ni,fv)
zvi,fzvi,f+gvi,fσvi,fvi,f zi,fv←zi,fv+gi,fv−σi,fvvi,f
nvi,fnvi,f+(gvi,f)2 ni,fv←ni,fv+(gi,fv)2
endfor endfor
endfor endfor
endfor endfor


多分类问题

  Softmax模型是LR在多分类上的推广,具体介绍戳这里。大致就是如果有 c c个类别,则模型参数为 c c个向量: Θ={w1,w2,,wc} Θ={w1,w2,…,wc},其中任意 wiRn wi∈Rn

  样本 x x属于类别 i i的概率:

P(y=i|x,Θ)=ewTixcj=1ewTjx P(y=i|x,Θ)=ewiTx∑j=1cewjTx

  FM解决多分类的方法同样是将线性部分 wTx wTx替换成复杂的  y^(x|Θ) y^(x|Θ),不过不再是一个  y^ y^,而是每一类别对应一个,共 c c个: y^1(x|Θ),,y^c(x|Θ) y^1(x|Θ),…,y^c(x|Θ)

  样本 x x属于类别 i i的概率也变成:

P(y=i|x,Θ)=ey^i(x|Θ)cj=1ey^j(x|Θ) P(y=i|x,Θ)=ey^i(x|Θ)∑j=1cey^j(x|Θ)

  模型参数一共 c c组,  Θ={Θ1,,Θc} Θ={Θ1,…,Θc},其中类别 i i对应一组参数  Θi={wi0,wi1,,win,vi1,1,,vin,k} Θi={w0i,w1i,…,wni,v1,1i,…,vn,ki}

  我们定义一个示性函数  1{} 1{⋅},大括号中表达式为真则值为1,表达式为假则值为0。这样就可以写出最优化问题:

argmaxΘ(x,y)Si=1cP(y=i|x,Θ)1{y=i}=argminΘ(x,y)Si=1c1{y=i}lnP(y=i|x,Θ) arg⁡maxΘ⁡∏(x,y)∈S∏i=1cP(y=i|x,Θ)1{y=i}=arg⁡minΘ−∑(x,y)∈S∑i=1c1{y=i}ln⁡P(y=i|x,Θ)

  每条样本  (x,y) (x,y) 的损失函数:
l(Θ|x,y)=i=1c1{y=i}lnP(y=i|x,Θ)=i=1c1{y=i}lney^i(x|Θ)cj=1ey^j(x|Θ)=i=1c1{y=i}(lnj=1cey^j(x|Θ)y^i(x|Θ))=lnj=1cey^j(x|Θ)i=1c1{y=i}y^i(x|Θ) l(Θ|x,y)=−∑i=1c1{y=i}ln⁡P(y=i|x,Θ)=−∑i=1c1{y=i}ln⁡ey^i(x|Θ)∑j=1cey^j(x|Θ)=∑i=1c1{y=i}(ln⁡∑j=1cey^j(x|Θ)−y^i(x|Θ))=ln⁡∑j=1cey^j(x|Θ)−∑i=1c1{y=i}y^i(x|Θ)

  梯度:
Θil(Θ|x,y)=ly^iΘiy^i(x|Θ) ∇Θil(Θ|x,y)=∂l∂y^i∇Θiy^i(x|Θ)

  而
ly^i=ey^i(x|Θ)cj=1ey^j(x|Θ)1{y=i}=P(y=i|x,Θ)1{y=i} ∂l∂y^i=ey^i(x|Θ)∑j=1cey^j(x|Θ)−1{y=i}=P(y=i|x,Θ)−1{y=i}

  所以有
Θil(Θ|x,y)=(P(y=i|x,Θ)1{y=i})Θiy^i(x|Θ) ∇Θil(Θ|x,y)=(P(y=i|x,Θ)−1{y=i})∇Θiy^i(x|Θ)

Θiy^i(x|Θ) ∇Θiy^i(x|Θ) 即求  y^i y^i 对  Θi Θi 中所有参数  {wi0,wi1,,win,vi1,1,,vin,k} {w0i,w1i,…,wni,v1,1i,…,vn,ki} 的偏导,这在二分类中我们已经给出。

  最后,仍然是套用FTRL的框架,只是每条样本更新的参数更多,不再细说,详见代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值