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中,勿扰:-)。
这里
。大致就是如果有$c$个类别,则模型参数为$c$个向量:$\Theta=\{w_1,w_2,…,w_c\}$,其中任意$w_i\in R^n$。
样本$x$属于类别$i$的概率:
$$
P(y=i|x,\Theta)=\frac{e^{w_i^Tx}}{\sum_{j=1}^ce^{w_j^Tx}}
$$
FM解决多分类的方法同样是将线性部分$w^Tx$替换成复杂的 $\hat{y}(x|\Theta)$,不过不再是一个 $\hat{y}$,而是每一类别对应一个,共$c$个:$\hat{y}_1(x|\Theta),…,\hat{y}_c(x|\Theta)$
样本$x$属于类别$i$的概率也变成:
$$
P(y=i|x,\Theta)=\frac{e^{\hat{y}_i(x|\Theta)}}{\sum_{j=1}^ce^{\hat{y}_j(x|\Theta)}}
$$
模型参数一共$c$组, $\Theta=\{\Theta_1,…,\Theta_c\}$,其中类别$i$对应一组参数 $\Theta_i=\{w_0^i,w_1^i,…,w_n^i,v_{1,1}^i,…,v_{n,k}^i\}$
我们定义一个示性函数 $1\{\cdot\}$,大括号中表达式为真则值为1,表达式为假则值为0。这样就可以写出最优化问题:
$$
\mathop{\arg\max}_{\Theta}\prod_{(x,y)\in S}\prod_{i=1}^cP(y=i|x,\Theta)^{1\{y=i\}}\\
=\mathop{\arg\min}_{\Theta}-\sum_{(x,y)\in S}\sum_{i=1}^c1\{y=i\}\ln P(y=i|x,\Theta)
$$
每条样本 $(x,y)$ 的损失函数:
$$
l(\Theta|x,y)=-\sum_{i=1}^c1\{y=i\}\ln P(y=i|x,\Theta)\\
=-\sum_{i=1}^c1\{y=i\}\ln \frac{e^{\hat{y}_i(x|\Theta)}}{\sum_{j=1}^ce^{\hat{y}_j(x|\Theta)}}\\
=\sum_{i=1}^c1\{y=i\}(\ln\sum_{j=1}^ce^{\hat{y}_j(x|\Theta)}-\hat{y}_i(x|\Theta))\\
=\ln\sum_{j=1}^ce^{\hat{y}_j(x|\Theta)}-\sum_{i=1}^c1\{y=i\}\hat{y}_i(x|\Theta)
$$
梯度:
$$
\nabla_{\Theta_i}l(\Theta|x,y)=\frac{\partial l}{\partial\hat{y}_i}\nabla_{\Theta_i}\hat{y}_i(x|\Theta)
$$
而
$$
\frac{\partial l}{\partial\hat{y}_i}=\frac{e^{\hat{y}_i(x|\Theta)}}{\sum_{j=1}^ce^{\hat{y}_j(x|\Theta)}}-1\{y=i\}\\
=P(y=i|x,\Theta)-1\{y=i\}
$$
所以有
$$
\nabla_{\Theta_i}l(\Theta|x,y)=(P(y=i|x,\Theta)-1\{y=i\})\nabla_{\Theta_i}\hat{y}_i(x|\Theta)
$$
$\nabla_{\Theta_i}\hat{y}_i(x|\Theta)$ 即求 $\hat{y}_i$ 对 $\Theta_i$ 中所有参数 $\{w_0^i,w_1^i,…,w_n^i,v_{1,1}^i,…,v_{n,k}^i\}$ 的偏导,这在二分类中我们已经给出。
最后,仍然是套用FTRL的框架,只是每条样本更新的参数更多,不再细说,详见代码。