排序模型在工业界已经有了很长时间的历史,从基于策略规则的人工指定特征权值开始,发展到LR线性模型,LR+GBDT半自动特征组合模型,再到FM自动二阶特征组合模型及深度学习模型等不断发展。其中FM系列模型占据比较重要的位置,本篇文章就FM模型进行分析和总结。
FM系列模型2——DeepFM及python(DeepCTR)实现
1,概述
在机器学习中,预测是一项基本的任务,所谓预测就是估计一个函数,该函数将一个n维的特征向量x映射到一个目标域T:
D
=
{
(
x
(
1
)
,
y
(
1
)
)
,
(
x
(
2
)
,
y
(
2
)
)
,
.
.
.
,
(
x
(
N
)
,
y
(
N
)
)
}
D =\{(x^{(1)},y^{(1)}),(x^{(2)},y^{(2)}),...,(x^{(N)},y^{(N)})\}
D={(x(1),y(1)),(x(2),y(2)),...,(x(N),y(N))}
在传统的线性模型中,每个特征都是独立的,如果需要考虑特征与特征之间的相互作用,可能需要人工对特征进行交叉组合。非线性SVM可以对特征进行核变换,但是在特征高度稀疏的情况下,并不能很好的进行学习。由于推荐系统是一个高度系数的数据场景,由此产生了FM系列算法,包括FM,FFM,DeepFM等算法。
推荐系统中还有一个比较重要的因素就是速度,比如容易并行,可解释、可拓展,这就是LR在工业界比较流行的原因之一。我们先回顾下线性模型和逻辑回归模型。
线性回归
y ^ ( x ) = w 0 + w 1 x 1 + w 2 x 2 + . . . + w n x n = w 0 + ∑ j = 1 n w i x i \begin{aligned} \hat{y}(x) = & w_0 + w_1x_1 + w_2x_2 + ...+ w_nx_n\\ =&w_0 + \sum_{j=1}^nw_ix_i \end{aligned} y^(x)==w0+w1x1+w2x2+...+wnxnw0+j=1∑nwixi
逻辑回归
核心函数:
h
θ
(
x
)
=
1
1
+
e
−
θ
x
h_{\theta}(x) = \frac{1}{1+e^{-\theta x}}
hθ(x)=1+e−θx1
对于一个样本:
P
(
y
=
1
∣
x
,
θ
)
=
h
θ
(
x
)
P
(
y
=
0
∣
x
,
θ
)
=
1
−
h
θ
(
x
)
\begin{aligned} P(y=1|x,\theta) =& h_{\theta}(x)\\ P(y=0|x,\theta) = &1-h_{\theta}(x) \end{aligned}
P(y=1∣x,θ)=P(y=0∣x,θ)=hθ(x)1−hθ(x)
那么进一步,样本x的概率表示为:
P
(
y
∣
x
;
θ
)
=
(
h
θ
(
x
)
)
y
(
1
−
h
θ
(
x
)
)
1
−
y
P(y|x;\theta)=(h_{\theta}(x))^y(1-h_{\theta}(x))^{1-y}
P(y∣x;θ)=(hθ(x))y(1−hθ(x))1−y
似然函数:
L
(
θ
)
=
∏
i
=
1
m
P
(
y
(
i
)
∣
x
(
i
)
;
θ
)
L(\theta) = \prod_{i=1}^mP(y^{(i)}|x^{(i)};\theta)
L(θ)=i=1∏mP(y(i)∣x(i);θ)
接下来,取对数,最大似然,最小loss:
J
(
θ
)
=
−
1
m
l
o
g
(
L
(
θ
)
)
J(\theta) = -\frac{1}{m}log(L(\theta))
J(θ)=−m1log(L(θ))
求导、梯度下降。。。
2,FM模型
我们说线性模型有个非常大的问题就是没有考虑特征之间的相互关系:
y ^ ( x ) = w 0 + w 1 x 1 + w 2 x 2 + . . . + w n x n = w 0 + ∑ j = 1 n w i x i \begin{aligned} \hat{y}(x) = & w_0 + w_1x_1 + w_2x_2 + ...+ w_nx_n\\ =&w_0 + \sum_{j=1}^nw_ix_i \end{aligned} y^(x)==w0+w1x1+w2x2+...+wnxnw0+j=1∑nwixi
事实上,如果特征之间相互关联,不同的特征组合可以提高模型的表达能力,而大多数情况下,特征之间都不是相互独立的。
为了表达这种关联特性,接下来,我们将函数
y
^
\hat{y}
y^改成:
y
^
(
x
)
=
w
0
+
∑
j
=
1
n
w
i
x
i
+
∑
i
=
1
n
−
1
∑
j
=
i
+
1
n
w
i
j
x
i
x
j
\begin{aligned} \hat{y}(x) = &w_0 + \sum_{j=1}^nw_ix_i + \sum_{i=1}^{n-1}\sum_{j=i+1}^{n}w_{ij}x_ix_j \end{aligned}
y^(x)=w0+j=1∑nwixi+i=1∑n−1j=i+1∑nwijxixj
这样两个特征之间的关系也考虑了,不过很遗憾,这种直接在 x i x i x_ix_i xixi前面配上系数 w i j w_{ij} wij的方式在稀疏数据面前有很大的缺陷。
在数据稀疏性普遍存在的实际应用场景中,二次项参数的训练是很困难的。其原因是:每个参数 w i j w_ij wij的训练需要大量 x i x_i xi和 x j x_j xj非零样本,由于样本数据本来就比较稀疏,满 x i x_i xi和 x j x_j xj都非零的样本将会非常少。训练样本的不足,很容易导致参数 w i j w_{ij} wij不准确,最终将严重影响模型的性能。一句话, W i j W_{ij} Wij只有在观察样本中有 x i x_i xi和 x j x_j xj的情况下才能优化得到,对于观察样本中未出现过的特征分量不能进行相应参数的评估.
对于推荐系统来讲,高度稀疏的数据场景太普遍了。为了克服这个困难我们引入辅助向量:
V i = ( v i 1 , v i 2 , . . . , v i k ) T V_i = (v_{i1},v_{i2},...,v_{ik})^T Vi=(vi1,vi2,...,vik)T
将w改为:
w
^
i
j
=
V
i
T
V
j
=
∑
l
=
1
k
v
i
l
v
j
l
\hat{w}_{ij}=V_i^TV_j=\sum_{l=1}^kv_{il}v_{jl}
w^ij=ViTVj=l=1∑kvilvjl
y ^ ( x ) = w 0 + ∑ j = 1 n w i x i + ∑ i = 1 n − 1 ∑ j = i + 1 n ( V i T V j ) x i x j \begin{aligned} \hat{y}(x) = &w_0 + \sum_{j=1}^nw_ix_i + \sum_{i=1}^{n-1}\sum_{j=i+1}^{n}(V_i^TV_j)x_ix_j \end{aligned} y^(x)=w0+j=1∑nwixi+i=1∑n−1j=i+1∑n(ViTVj)xixj
目前这样的表达式的复杂度是O(n^2),但是我们可以对其进行分解,使得复杂度变为O(n)
二次项的参数数量减少为
k
n
kn
kn个,远少于多项式模型的参数数量。另外,参数因子化使得
x
h
x
i
x_hx_i
xhxi 的参数和
x
i
x
j
x_ix_j
xixj 的参数不再是相互独立的,因此我们可以在样本稀疏的情况下相对合理地估计FM的二次项参数。具体来说,
x
h
x
i
x_hx_i
xhxi 和 $ x_ix_j$ 的系数分别为
v
h
,
v
i
v_h,v_i
vh,vi和
v
i
,
v
j
v_i,v_j
vi,vj ,它们之间有共同项
v
i
v_i
vi 。也就是说,所有包含“
x
i
x_i
xi 的非零组合特征”(存在某个
j
≠
i
j \not=i
j=i ,使得
x
i
x
j
≠
0
x_ix_j\not=0
xixj=0 )的样本都可以用来学习隐向量
v
i
v_i
vi,这很大程度上避免了数据稀疏性造成的影响。而在多项式模型中,
w
h
i
w_{hi}
whi 和
w
i
j
w_{ij}
wij是相互独立的。
到这里,我们就可以通过设置loss函数,梯度下降等方法进行优化了。
3,FFM
FFM是FM的升级版模型,引入了field的概念。FFM把相同性质的特征归于同一个field。在FFM中,每一维特征 x i x_i xi,针对每一种field f j f_j fj,都会学习到一个隐向量 V i , f j V_{i,f_j} Vi,fj,因此,隐向量不仅与特征相关,也与field相关。
设样本一共有n个特征, f 个field,那么FFM的二次项有nf个隐向量。而在FM模型中,每一维特征的隐向量只有一个。FM可以看做FFM的特例,即把所有特征都归属到同一个field中。
如果隐向量的长度为k,那么FFM的二次项参数数量为nfk,远多于FM模型。此外由于隐向量与field相关,FFM二次项并不能够化简,时间复杂度为O(kn^2)。需要注意的是由于FFM中的latent vector只需要学习特定的field,所以通常
K
F
F
M
<
<
K
F
M
K_{FFM} << K_{FM}
KFFM<<KFM。
看一个经常用的例子:
其中,红色部分对应的是Field,来自于原始特征的个数;
蓝色部分对应的是feature,来自于原始特征onehot之后的个数。(连续型特征不用one-hot)
对于特征Feature:User=YuChin,有Movie=3Idiots、Genre=Comedy、Genre=Drama、Price四项要进行交叉组合:
绿色部分为对应特征one-hot之后的值,出现为1,不出现为0。对于连续型变量的处理,这里采用的是使用实际值,当然,也可以对连续型变量离散化处理,再进行one-hot。
可以看到3和4的field是一样的,特征1与特征3、特征1与特征4交叉的时候涉及到的都是域2和域3。
注意,在实际应用中,为了使用FFM方法,所有的特征必须转换成“field_id:feat_id:value”格式,field_id代表特征所属field的编号,feat_id是特征编号,value是特征的值。
优点:FMM更加细致的刻画了特征
缺点:但是使参数增加了F倍(F为域的数量),也相应的增加了训练难度,内存消耗大。而且不能像FM那样分解,时间复杂度高
4,python xlearn 实现
实现FM & FFM的最流行的python库有:LibFM、LibFFM、xlearn和tffm。其中,xLearn是一款高性能,易于使用且可扩展的机器学习软件包,包括FM和FFM模型,可用于大规模解决机器学习问题。xlearn比libfm和libffm库快得多,并为模型测试和调优提供了更好的功能。这里以xlearn实现FM和FFM算法。
建议大家不要用pip直接安装(pip install xlearn),会报错,到这里直接下载对应版本的whl格式文件安装。xlearn的API,数据可以从这下载
data_path = 'E:\PythonWorkSpace\\backups\\xlearn\\xlearn-master\demo\classification\criteo_ctr\small_train.txt'
import xlearn as xl
# Training task
ffm_model = xl.create_ffm() # Use field-aware factorization machine (ffm)
ffm_model.setTrain(data_path) # Set the path of training data
# parameter:
# 0. task: binary classification
# 1. learning rate : 0.2
# 2. regular lambda : 0.002
param = {'task':'binary', 'lr':0.2, 'lambda':0.002}
# param = {'task':'reg', 'lr':0.2, 'lambda':0.002}
# Train model
ffm_model.fit(param, "./model.out")
对于 LR 和 FM 算法而言,我们的输入数据格式必须是 CSV 或者 libsvm. 对于 FFM 算法而言,我们的输入数据必须是 libffm 格式。
libsvm format:
y index_1:value_1 index_2:value_2 ... index_n:value_n
0 0:0.1 1:0.5 3:0.2 ...
0 0:0.2 2:0.3 5:0.1 ...
1 0:0.2 2:0.3 5:0.1 ...
CSV format:
y value_1 value_2 .. value_n
0 0.1 0.2 0.2 ...
1 0.2 0.3 0.1 ...
0 0.1 0.2 0.4 ...
libffm format:
y field_1:index_1:value_1 field_2:index_2:value_2 ...
0 0:0:0.1 1:1:0.5 2:3:0.2 ...
0 0:0:0.2 1:2:0.3 2:5:0.1 ...
1 0:0:0.2 1:2:0.3 2:5:0.1 ...
这里我们采用FFM模型,我们看下所用的部分数据:
1 0:0:0.3651 2:1163:0.3651 3:8672:0.3651 4:2183:0.3651 5:2332:0.3651 6:185:0.3651 7:2569:0.3651 8:8131:0.3651 9:5483:0.3651 10:215:0.3651 11:1520:0.3651 12:1232:0.3651 13:2738:0.3651 14:2935:0.3651 15:5428:0.3651 17:2434:0.50000 16:7755:0.50000
0 1:1988:0.3651 2:400:0.3651 3:1243:0.3651 4:2183:0.3651 5:2847:0.3651 6:185:0.3651 7:7196:0.3651 8:1343:0.3651 9:3899:0.3651 10:9493:0.3651 11:1520:0.3651 12:9714:0.3651 13:2738:0.3651 14:2935:0.3651 15:5428:0.3651 17:7148:0.50000 16:1828:0.50000
0 1:7633:0.3651 2:8195:0.3651 3:9952:0.3651 4:9619:0.3651 5:9882:0.3651 6:185:0.3651 7:3479:0.3651 8:3373:0.3651 9:7873:0.3651 10:5989:0.3651 11:1520:0.3651 12:4520:0.3651 13:2738:0.3651 14:2935:0.3651 15:5428:0.3651 17:3723:0.50000 16:928:0.50000
1 1:7593:0.3651 2:9126:0.3651 3:9952:0.3651 4:2183:0.3651 5:5525:0.3651 6:5918:0.3651 7:1969:0.3651 8:3240:0.3651 9:3899:0.3651 10:3387:0.3651 11:1520:0.3651 12:9714:0.3651 13:2738:0.3651 14:2935:0.3651 15:5428:0.3651 17:8692:0.50000 16:1861:0.50000
0 1:4222:0.3651 2:1163:0.3651 3:8672:0.3651 4:2183:0.3651 5:3780:0.3651 6:185:0.3651 7:2569:0.3651 8:8131:0.3651 9:3899:0.3651 10:215:0.3651 11:1520:0.3651 12:2565:0.3651 13:2738:0.3651 14:8813:0.3651 15:6145:0.3651 17:2434:0.50000 16:7755:0.50000
本数据集是二分类(0,1),每个特征值都表示为“field_id:feat_id:value”的形式
结果如下,共有18个域,9991个特征
[ WARNING ] Validation file(dataset) not found, xLearn has already disable early-stopping.
[------------] xLearn uses 8 threads for training task.
[ ACTION ] Read Problem ...
[------------] First check if the text file has been already converted to binary format.
[------------] Binary file (E:\PythonWorkSpace\backups\xlearn\xlearn-master\demo\classification\criteo_ctr\small_train.txt.bin) NOT found. Convert text file to binary file.
[------------] Number of Feature: 9991
[------------] Number of Field: 18
[------------] Time cost for reading problem: 0.19 (sec)
[ ACTION ] Initialize model ...
[------------] Model size: 5.56 MB
[------------] Time cost for model initial: 0.03 (sec)
[ ACTION ] Start to train ...
[------------] Epoch Train log_loss Time cost (sec)
[ 10% ] 1 0.606436 0.00
[ 20% ] 2 0.536963 0.00
[ 30% ] 3 0.515709 0.00
[ 40% ] 4 0.507723 0.00
[ 50% ] 5 0.492301 0.00
[ 60% ] 6 0.481908 0.00
[ 70% ] 7 0.471874 0.00
[ 80% ] 8 0.464164 0.00
[ 90% ] 9 0.455754 0.00
[ 100% ] 10 0.448856 0.00
[ ACTION ] Start to save model ...
[------------] Model file: ./model.out
[------------] Time cost for saving model: 0.02 (sec)
[ ACTION ] Finish training
[ ACTION ] Clear the xLearn environment ...
[------------] Total time cost: 0.30 (sec)
我们发现,xLearn 训练过后在当前文件夹下产生了一个叫 model.out 的新文件。这个文件用来存储训练后的模型,我们可以用这个模型在未来进行预测:
ffm_model.setSigmoid()
# ffm_model.setSign() #将结果转化为0,1
ffm_model.setTest("./small_test.txt")
ffm_model.predict("./model.out", "./output.txt")
结果:
0.171177
0.403148
0.342638
0.406686
0.245445
0.309569
0.223888
0.192466
0.362394
0.25107
0.407777
...
更多的使用方法,大家可以到相应API内部查看。
参考资料:
https://www.cnblogs.com/pinard/p/6370127.html#!comments
https://zhuanlan.zhihu.com/p/37963267
https://blog.csdn.net/pql925/article/details/79021464
https://www.jianshu.com/p/27679d6f3949
https://www.jianshu.com/p/1db7404689c5
https://www.cnblogs.com/wkang/p/9788012.html