全栈工程师开发手册 (作者:栾鹏)
python教程全解

FM问题来源

CTR/CVR预测时,用户的性别、职业、教育水平、品类偏好,商品的品类等,经过One-Hot编码转换后都会导致样本数据的稀疏性。特别是商品品类这种类型的特征,如商品的末级品类约有550个,采用One-Hot编码生成550个数值特征,但每个样本的这550个特征,有且仅有一个是有效的(非零)。由此可见,数据稀疏性是实际问题中不可避免的挑战。

One-Hot编码的另一个特点就是导致特征空间大。例如,商品品类有550维特征,一个categorical特征转换为550维数值特征,特征空间剧增。

同时通过观察大量的样本数据可以发现,某些特征经过关联之后,与label之间的相关性就会提高。例如,“USA”与“Thanksgiving”、“China”与“Chinese New Year”这样的关联特征,对用户的点击有着正向的影响。换句话说,来自“China”的用户很可能会在“Chinese New Year”有大量的浏览、购买行为,而在“Thanksgiving”却不会有特别的消费行为。这种关联特征与label的正向相关性在实际问题中是普遍存在的,如“化妆品”类商品与“女”性,“球类运动配件”的商品与“男”性,“电影票”的商品与“电影”品类偏好等。因此,引入两个特征的组合是非常有意义的。

FM基本原理

多项式模型是包含特征组合的最直观的模型。在多项式模型中,特征 xix_ixi​ 和 xjx_jxj​ 的组合采用 xixjx_ix_jxi​xj​表示,即 xix_ixi​ 和 xjx_jxj​ 都非零时,组合特征 xixjx_ix_jxi​xj​ 才有意义。从对比的角度,本文只讨论二阶多项式模型。模型的表达式如下

y(x)=w0+∑i=1nwixi+∑i=1n∑j=i+1nwijxixj(1)y(x) = w_0+ \sum_{i=1}^n w_i x_i + \sum_{i=1}^n \sum_{j=i+1}^n w_{ij} x_i x_j \tag{1}y(x)=w0​+i=1∑n​wi​xi​+i=1∑n​j=i+1∑n​wij​xi​xj​(1)

其中,nnn 代表样本的特征数量,xix_ixi​ 是第 iii 个特征的值,w0w_0w0​、wiw_iwi​、wijw_{ij}wij​是模型参数。

从公式(1)可以看出,组合特征的参数一共有 n(n−1)2\frac{n(n−1)}{2}2n(n−1)​个,任意两个参数都是独立的。然而,在数据稀疏性普遍存在的实际应用场景中,二次项参数的训练是很困难的。其原因是,每个参数 wijw_{ij}wij​的训练需要大量 xix_ixi​ 和 xjx_jxj​ 都非零的样本;由于样本数据本来就比较稀疏,满足“xix_ixi​ 和 xjx_jxj​ 都非零”的样本将会非常少。训练样本的不足,很容易导致参数 wijw_{ij}wij​ 不准确,最终将严重影响模型的性能。

系数矩阵分解

那么,如何解决二次项参数的训练问题呢?矩阵分解提供了一种解决思路。与在model-based的协同过滤中,一个rating矩阵可以分解为user矩阵和item矩阵。

对于对称矩阵W,
W=(ω11ω12...ω1nω21ω22...ω2n⋮⋮⋱⋮ωn1ωn2...ωnn)n×nW= \begin{pmatrix} \omega_{11} & \omega_{12}& ... &\omega_{1n} \\ \omega_{21} & \omega_{22}& ... &\omega_{2n} \\ \vdots &\vdots &\ddots &\vdots\\ \omega_{n1} & \omega_{n2}& ... &\omega_{nn} \\ \end{pmatrix}_{n\times n}W=⎝⎜⎜⎜⎛​ω11​ω21​⋮ωn1​​ω12​ω22​⋮ωn2​​......⋱...​ω1n​ω2n​⋮ωnn​​⎠⎟⎟⎟⎞​n×n​

由于直接求解W不方便,因此我们引入隐变量V:
V=(v11v12...v1kv21v22...v2k⋮⋮⋱⋮vn1vn2...vnk)n×k=(V1TV2T⋯VnT)V= \begin{pmatrix} v_{11} & v_{12}& ... &v_{1k} \\ v_{21} & v_{22}& ... &v_{2k} \\ \vdots &\vdots &\ddots &\vdots\\ v_{n1} & v_{n2}& ... &v_{nk} \\ \end{pmatrix}_{n\times k}=\begin{pmatrix} V_1^T\\ V_2^T\\ \cdots \\ V_n^T\\ \end{pmatrix}V=⎝⎜⎜⎜⎛​v11​v21​⋮vn1​​v12​v22​⋮vn2​​......⋱...​v1k​v2k​⋮vnk​​⎠⎟⎟⎟⎞​n×k​=⎝⎜⎜⎛​V1T​V2T​⋯VnT​​⎠⎟⎟⎞​

满足
VVT=WVV^T = WVVT=W

VVV 的第$ j列列列v_j便是第便是第便是第 j $维特征的隐向量。换句话说,每个参数 wij=⟨vi,vj⟩w_{ij}=⟨v_i,v_j⟩wij​=⟨vi​,vj​⟩,这就是FM模型的核心思想。因此,FM的模型方程为(本文不讨论FM的高阶形式)

y(x)=w0+∑i=1nwixi+∑i=1n∑j=i+1n<vi,vj>xixj(2)y(x) = w_0+ \sum_{i=1}^n w_i x_i + \sum_{i=1}^n \sum_{j=i+1}^n<v_i, v_j >x_i x_j \tag{2}y(x)=w0​+i=1∑n​wi​xi​+i=1∑n​j=i+1∑n​<vi​,vj​>xi​xj​(2)

参数个数

$ v_i $是第 $ i $ 维特征的隐向量,$ <·,·> $ 代表向量点积。隐向量的长度为 $ k ( k << n $),包含 $ k $ 个描述特征的因子。根据公式2,二次项的参数数量减少为 $ kn $个,远少于多项式模型的参数数量。另外,参数因子化使得 $ x_h x_i $ 的参数和 $ x_i x_j $ 的参数不再是相互独立的,因此我们可以在样本稀疏的情况下相对合理地估计FM的二次项参数。具体来说,$ x_h x_i $ 和 $ x_i x_j $ 的系数分别为 $ <v_h,v_i> $ 和 $ <v_i, v_j>$,它们之间有共同项 $ v_i 。也就是说,所有包含“。也就是说,所有包含“。也就是说,所有包含“ x_i $ 的非零组合特征”(存在某个 $ j\neq i $,使得 $ x_i x_j \neq 0 $)的样本都可以用来学习隐向量 $ v_i ,这很大程度上避免了数据稀疏性造成的影响。而在多项式模型中,,这很大程度上避免了数据稀疏性造成的影响。而在多项式模型中,,这很大程度上避免了数据稀疏性造成的影响。而在多项式模型中, w_{hi} $ 和 $ w_{ij} $ 是相互独立的。

预测时间复杂度

显而易见,公式(2)是一个通用的拟合方程,可以采用不同的损失函数用于解决回归、二元分类等问题,比如可以采用MSE(Mean Square Error)损失函数来求解回归问题,也可以采用Hinge/Cross-Entropy损失来求解分类问题。当然,在进行二元分类时,FM的输出需要经过sigmoid变换,这与Logistic回归是一样的。

当我们已经求出所有参数以后,对新输入对象进行预测时,FM的计算复杂度是 O(kn2)O(kn^2)O(kn2)。但是,通过公式(3)的等式,FM的二次项可以化简,其复杂度可以优化到 O(kn)O(kn)O(kn)。由此可见,FM可以在线性时间对新样本作出预测

∑i=1n∑j=i+1n⟨vi,vj⟩xixj=12∑f=1k((∑i=1nvi,fxi)2−∑i=1nvi,f2xi2)(3)\sum_{i=1}^n \sum_{j=i+1}^n \langle \mathbf{v}_i, \mathbf{v}_j \rangle x_i x_j = \frac{1}{2} \sum_{f=1}^k \left(\left( \sum_{i=1}^n v_{i, f} x_i \right)^2 - \sum_{i=1}^n v_{i, f}^2 x_i^2 \right) \tag{3}i=1∑n​j=i+1∑n​⟨vi​,vj​⟩xi​xj​=21​f=1∑k​⎝⎛​(i=1∑n​vi,f​xi​)2−i=1∑n​vi,f2​xi2​⎠⎞​(3)

梯度下降法

利用SGD(Stochastic Gradient Descent)训练模型。模型各个参数的梯度如下

∂∂θy(x)={1,ifθisw0xi,ifθiswixi∑j=1nvj,fxj−vi,fxi2,ifθisvi,f\frac{\partial}{\partial\theta} y (\mathbf{x}) = \left\{\begin{array}{ll} 1, & \text{if}\; \theta\; \text{is}\; w_0 \\ x_i, & \text{if}\; \theta\; \text{is}\; w_i \\ x_i \sum_{j=1}^n v_{j, f} x_j - v_{i, f} x_i^2, & \text{if}\; \theta\; \text{is}\; v_{i, f} \end{array}\right.∂θ∂​y(x)=⎩⎨⎧​1,xi​,xi​∑j=1n​vj,f​xj​−vi,f​xi2​,​ifθisw0​ifθiswi​ifθisvi,f​​

其中,$ v_{j, f} $ 是隐向量 $ v_j $ 的第 $ f $ 个元素。由于 $ \sum_{j=1}^n v_{j, f} x_j $ 只与 $ f $ 有关,而与 $ i $ 无关,在每次迭代过程中,只需计算一次所有 $ f $ 的 $ \sum_{j=1}^n v_{j, f} x_j $,就能够方便地得到所有 $ v_{i, f} $ 的梯度。显然,计算所有 $ f $ 的 $ \sum_{j=1}^n v_{j, f} x_j $ 的复杂度是 $ O(kn) $;已知 $ \sum_{j=1}^n v_{j, f} x_j $ 时,计算每个参数梯度的复杂度是 $ O(1) $;得到梯度后,更新每个参数的复杂度是 $ O(1) $;模型参数一共有 $ nk + n + 1 $ 个。因此,FM参数训练的复杂度也是 $ O(kn) $。综上可知,FM可以在线性时间训练和预测,是一种非常高效的模型。

FFM原理

背景及基本原理

在FM模型中,每一个特征会对应一个隐变量,但在FFM模型中,认为应该将特征分为多个field,每个特征对应每个field分别有一个隐变量。

举个例子,我们的样本有3种类型的字段:publisher, advertiser, gender,分别可以代表媒体,广告主或者是具体的商品,性别。其中publisher有5种数据,advertiser有10种数据,gender有男女2种,经过one-hot编码以后,每个样本有17个特征,其中只有3个特征非空。简单来说,同一个categorical特征经过One-Hot编码生成的数值特征都可以放到同一个field。

如果使用FM模型,则17个特征,每个特征对应一个隐变量。
如果使用FFM模型,则17个特征,每个特征对应3个隐变量,即每个类型对应一个隐变量,具体而言,就是对应publisher, advertiser, gender三个field各有一个隐变量。

在FFM中,每一维特征 xix_ixi​,针对其它特征的每一种field fjf_jfj​(其中 fjf_jfj​表示第j维特征所属的field),都会学习一个隐向量 vi,fjv_{i,f_j}vi,fj​​。因此,隐向量不仅与特征相关,也与field相关。也就是说,“Day=26/11/15”这个特征与“国家”特征和“广告类型"特征进行关联的时候使用不同的隐向量,这与“国家”和“广告类型”的内在差异相符

假设样本的 nnn 个特征属于 fff 个field,那么FFM的二次项有 nfnfnf个隐向量。而在FM模型中,每一维特征的隐向量只有一个。FM可以看作FFM的特例,是把所有特征都归属到一个field时的FFM模型。根据FFM的field敏感特性,可以导出其模型方程。

y(x)=w0+∑i=1nwixi+∑i=1n∑j=i+1n<vi,fj,vj,fi>xixj(4)y(x) = w_0 + \sum_{i=1}^n w_i x_i + \sum_{i=1}^n \sum_{j=i+1}^n < v_{i, f_j}, v_{j, f_i} > x_i x_j \tag{4}y(x)=w0​+i=1∑n​wi​xi​+i=1∑n​j=i+1∑n​<vi,fj​​,vj,fi​​>xi​xj​(4)

其中,fjf_jfj​ 是第 jjj 个特征所属的field。如果隐向量的长度为 kkk,那么FFM的二次参数有 nfknfknfk 个,远多于FM模型的 nknknk 个。此外,由于隐向量与field相关,FFM二次项并不能够化简,其预测复杂度是 O(kn2)O(kn^2)O(kn2)。

下面以一个例子简单说明FFM的特征组合方式[9]。输入记录如下

下面以一个例子简单说明FFM的特征组合方式[9]。输入记录如下

用户 电影 电影类型 价格
user1 三傻 喜剧, 戏剧 $9.99

这条记录可以编码成5个特征,其中“电影类型=喜剧”和“电影类型=戏剧”属于同一个field,“Price”是数值型,不用One-Hot编码转换。为了方便说明FFM的样本格式,我们将所有的特征和对应的field映射成整数编号。

field name Field index Feature name Feature index
User 1 User=YuChin 1
Movie 2 Movie=3Idiots 2
Genre 3 Genre=Comedy 3
Genre=Drama 4
Price 4 Price 5

那么,FFM的组合特征有10项,如下图所示。
KaTeX parse error: No such environment: align* at position 7: \begin{̲a̲l̲i̲g̲n̲*̲}̲\begin{array}{r…

二次项的系数是通过与特征field相关的隐向量点积得到的,二次项共有 n(n−1)2\frac{n(n−1)}{2}2n(n−1)​ 个。

事实上,在大多数情况下,FFM模型只保留了二次项部分,省略常数项和一次项,即:

ϕ(V,x)=∑i=1n∑j=i+1n<vi,fj,vj,fi>xixj=∑i=1n∑j=i+1n(vi,fjTvj,fi)xixj\phi(V,x) = \sum_{i=1}^n \sum_{j=i+1}^n < v_{i, f_j}, v_{j, f_i} > x_i x_j =\sum_{i=1}^n \sum_{j=i+1}^n (v^T_{i, f_j}v_{j, f_i} ) x_i x_j ϕ(V,x)=i=1∑n​j=i+1∑n​<vi,fj​​,vj,fi​​>xi​xj​=i=1∑n​j=i+1∑n​(vi,fj​T​vj,fi​​)xi​xj​

最优化问题

FFM模型采用logistic loss作为损失函数,和L2惩罚项,因此只能用于二元分类问题。根据逻辑回归的损失函数及分析,可以得出FFM的最优化问题为:

min⁡v∑i=1Llog⁡(1+exp⁡{−yiϕ(v,xi)})+λ2∥v∥2\min_{\mathbf{v}} \sum_{i=1}^L \log \big( 1 + \exp\{ -y_i \phi (\mathbf{v}, \mathbf{x}_i ) \} \big) + \frac{\lambda}{2} \| \mathbf{v} \|^2vmin​i=1∑L​log(1+exp{−yi​ϕ(v,xi​)})+2λ​∥v∥2

中,yi∈{−1,1}y_i∈\{−1,1\}yi​∈{−1,1} 是第 iii 个样本的label,LLL 是训练样本数量,λλλ是惩罚项系数。模型采用SGD优化

FFM代码实现

符号约定:
nnn:特征的维数

mmm:域的个数

kkk:隐向量的维度

jjj:在特征中的下标

fff:在域中的下标

ddd:在隐向量中的下标

lll:样本的总数

粗体字母表示向量或矩阵

特征组合

最基本的线性加权

ϕLM(w,x)=∑i=1nwixi\phi_{LM}(\textbf{w},\textbf{x})=\sum_{i=1}^n{w_ix_i}ϕLM​(w,x)=i=1∑n​wi​xi​

任意特征两两组合

ϕpoly2(w,x)=∑j1=1n∑j2=j1+1nwj1,j2xj1xj2\phi_{poly2}(\textbf{w},\textbf{x})=\sum_{j1=1}^n{\sum_{j2=j1+1}^n{w_{j1,j2}x_{j1}x_{j2}}}ϕpoly2​(w,x)=j1=1∑n​j2=j1+1∑n​wj1,j2​xj1​xj2​

www是一个对称方阵,即wj1,j2=wj2,j1w_{j1,j2}=w_{j2,j1}wj1,j2​=wj2,j1​,可以用矩阵分解法来拟合www。

wj1,j2=vj1⋅vj2=vj2⋅vj1=wj2,j1w_{j1,j2}=\textbf{v}_{j1}\cdot\textbf{v}_{j2}=\textbf{v}_{j2}\cdot\textbf{v}_{j1}=w_{j2,j1}wj1,j2​=vj1​⋅vj2​=vj2​⋅vj1​=wj2,j1​

矩阵www的规模是n×nn×nn×n,矩阵vvv的规模是n×kn×kn×k,k≪nk≪nk≪n。实际上我们已经推导出了因子分解法。

因子分解法FM
ϕFM(w,x)=∑j1=1n∑j2=j1+1nwj1⋅wj2xj1xj2\phi_{FM}(\textbf{w},\textbf{x})=\sum_{j1=1}^n{\sum_{j2=j1+1}^n{\textbf{w}_{j1}\cdot \textbf{w}_{j2}x_{j1}x_{j2}}}ϕFM​(w,x)=j1=1∑n​j2=j1+1∑n​wj1​⋅wj2​xj1​xj2​

这里的wjw_jwj​相当于上面的vjv_jvj​。

域感知的因子分解法FFM

ϕFFM(w,x)=∑j1=1n∑j2=j1+1nwj1,f2⋅wj2,f1xj1xj2\phi_{FFM}(\textbf{w},\textbf{x})=\sum_{j1=1}^n{\sum_{j2=j1+1}^n{\textbf{w}_{j1,f2}\cdot \textbf{w}_{j2,f1}x_{j1}x_{j2}}}ϕFFM​(w,x)=j1=1∑n​j2=j1+1∑n​wj1,f2​⋅wj2,f1​xj1​xj2​

在FM中w是规模为n×kFMn×k_{FM}n×kFM​的二维矩阵,而在FFM中www是规模为n×m×kFFMn×m×k_{FFM}n×m×kFFM​的三维矩阵,kFFM≪kFMk_{FFM}≪k_{FM}kFFM​≪kFM​。

逻辑回归二分类

决策函数

y^=11+exp(−ϕFFM(w,x))\hat{y}=\frac{1}{1+exp(-\phi_{FFM}(\textbf{w},\textbf{x}))}y^​=1+exp(−ϕFFM​(w,x))1​

带L2正则的目标函数

min⁡wλ2∥w∥22+∑i=1llog(1+exp(−yiϕFFM(w,xi)))\min_{\textbf{w}}\;\;\frac{\lambda}{2}\parallel w\parallel_2^2+\sum_{i=1}^llog(1+exp(-y_i\phi_{FFM}(\textbf{w},\textbf{x}_i)))wmin​2λ​∥w∥22​+i=1∑l​log(1+exp(−yi​ϕFFM​(w,xi​)))

其中yi∈{−1,1}y_i∈\{−1,1\}yi​∈{−1,1},注意,虽然在预测结果出来的是0-1之间数,但是训练时的y值取-1或1,所以当训练结束后,及测试集上测试,评估模型时,要将测试集的y值转换为0-1。

在SGD中每次只需要考虑一个样本的损失,此时目标函数为

min⁡wλ2∥w∥22+log(1+exp(−yϕFFM(w,x)))\min_{\textbf{w}}\;\;\frac{\lambda}{2}\parallel w\parallel_2^2+log(1+exp(-y\phi_{FFM}(\textbf{w},\textbf{x})))wmin​2λ​∥w∥22​+log(1+exp(−yϕFFM​(w,x)))

梯度

gj1,f2=λ⋅wj1,f2+κ⋅wj2,f1xj1xj2\textbf{g}_{j1,f2}=\lambda\cdot\textbf{w}_{j1,f2}+\kappa\cdot\textbf{w}_{j2,f1}x_{j1}x_{j2}gj1,f2​=λ⋅wj1,f2​+κ⋅wj2,f1​xj1​xj2​

gj2,f1=λ⋅wj2,f1+κ⋅wj1,f2xj1xj2\textbf{g}_{j2,f1}=\lambda\cdot\textbf{w}_{j2,f1}+\kappa\cdot\textbf{w}_{j1,f2}x_{j1}x_{j2}gj2,f1​=λ⋅wj2,f1​+κ⋅wj1,f2​xj1​xj2​

梯度之所会这么简单,依赖一个很重要的前提:同一个域下的各个特征只有一个是非0值。

其中

κ=∂log(1+exp(−yϕFFM(w,x)))∂ϕFFM(w,x)=−y1+exp(yϕFFM(w,x))\kappa=\frac{\partial log(1+exp(-y\phi_{FFM}(\textbf{w},\textbf{x})))}{\partial\phi_{FFM}(\textbf{w},\textbf{x})}=\frac{-y}{1+exp(y\phi_{FFM}(\textbf{w},\textbf{x}))}κ=∂ϕFFM​(w,x)∂log(1+exp(−yϕFFM​(w,x)))​=1+exp(yϕFFM​(w,x))−y​

AdaGrad更新w

(Gj1,f2)d←(Gj1,f2)d+(gj1,f2)d2(G_{j1,f2})_d\gets(G_{j1,f2})_d+(g_{j1,f2})_d^2(Gj1,f2​)d​←(Gj1,f2​)d​+(gj1,f2​)d2​
(Gj2,f1)d←(Gj2,f1)d+(gj2,f1)d2(G_{j2,f1})_d\gets(G_{j2,f1})_d+(g_{j2,f1})_d^2(Gj2,f1​)d​←(Gj2,f1​)d​+(gj2,f1​)d2​
(wj1,f2)d←(wj1,f2)d−η(Gj1,f2)d(gj1,f2)d(w_{j1,f2})_d\gets(w_{j1,f2})_d-\frac{\eta}{\sqrt{(G_{j1,f2})_d}}(g_{j1,f2})_d(wj1,f2​)d​←(wj1,f2​)d​−(Gj1,f2​)d​​η​(gj1,f2​)d​
(wj2,f1)d←(wj2,f1)d−η(Gj2,f1)d(gj2,f1)d(w_{j2,f1})_d\gets(w_{j2,f1})_d-\frac{\eta}{\sqrt{(G_{j2,f1})_d}}(g_{j2,f1})_d(wj2,f1​)d​←(wj2,f1​)d​−(Gj2,f1​)d​​η​(gj2,f1​)d​

初始化Gd=1G_d=1Gd​=1,这样在计算ηGd\frac{\eta}{\sqrt{G_d}}Gd​​η​时既可以防止分母为0,又可以避免该项太大或太小。

ηηη是学习率,通常可取0.01。

初始的www可以从均匀分布中抽样w∼U(0,1k)\textbf{w}\sim U(0,\frac{1}{\sqrt{k}})w∼U(0,k​1​)
实现发现将每个xxx归一化,即模长为1,在测试集得到的准确率会稍微好一点且对参数不太敏感。

FM代码示例

这里我们使用pyfm库。

FM和FFM的优势在于处理标称属性数据集再one-hot编码特征时形成的稀疏特征矩阵。所以pyfm库处理的矩阵,必须为稀疏矩阵。

import numpy as np
from sklearn.feature_extraction import DictVectorizer
from sklearn.model_selection import train_test_split
import pylibfm
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification
import  scipy.sparse
from sklearn.metrics import log_lossX, y = make_classification(n_samples=1000,n_features=100, n_clusters_per_class=1)  # 1000个样本,100个特征,默认2分类# 直接转化为稀疏矩阵,对有标称属性的数据集不能处理。
# X = scipy.sparse.csr_matrix(X)
# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42)# 由于大部分情况下,数据特征都是标称属性,所以需要先转化为字典,再转化稀疏矩阵。(转化为系数矩阵的过程中标称数据自动one-hot编码,数值属性保留)
data = [ {v: k for k, v in dict(zip(i, range(len(i)))).items()}  for i in X]  # 对每个样本转化为一个字典,key为特征索引(0-99),value为特征取值
X_train, X_test, y_train, y_test = train_test_split(data, y, test_size=0.1, random_state=42)v = DictVectorizer()
X_train = v.fit_transform(X_train)  # 转化为稀疏矩阵的形式,fm算法只能是被这种格式
X_test = v.transform(X_test)  # 转化为稀疏矩阵的形式,fm算法只能是被这种格式
# print(X_train.toarray())   # 打印二维矩阵形式# 建模、训练、预测、评估
fm = pylibfm.FM(num_factors=50, num_iter=10, verbose=True, task="classification", initial_learning_rate=0.0001, learning_rate_schedule="optimal")
fm.fit(X_train,y_train)
y_pred_pro = fm.predict(X_test)  # 预测正样本概率
print("fm算法 验证集log损失: %.4f" % log_loss(y_test,y_pred_pro))lr = LogisticRegression(verbose=True)
lr.fit(X_train,y_train)
y_pred_pro = lr.predict(X_test)  # 预测正样本概率
print("逻辑回归 验证集log损失: %.4f" % log_loss(y_test,y_pred_pro))
#

输出结果中
fm算法 验证集log损失: 1.8714
[LibLinear]逻辑回归 验证集log损失: 7.2532

FFM代码实例

import numpy as npnp.random.seed(0)
import math
from logistic import Logistic# 该类表示一个样本的一个特征
class FFM_Node(object):'''通常x是高维稀疏向量,所以用链表来表示一个x,链表上的每个节点是个3元组(j,f,v),表示一个样本x的一个非0特征'''__slots__ = ['j', 'f', 'v']  # 按元组(而不是字典)的方式来存储类的成员属性def __init__(self, j, f, v):''':param j: Feature index (0 to n-1):param f: Field index (0 to m-1):param v: value'''self.j = jself.f = fself.v = vclass FFM(object):def __init__(self, m, n, k, eta, lambd):# m 域个数,n特征个数,k隐变量维度,eta学习速率,lambd正则系数self.m = mself.n = nself.k = k# 超参数self.eta = etaself.lambd = lambd# 初始化三维权重矩阵w~U(0,1/sqrt(k))self.w = np.random.rand(n, m, k) / math.sqrt(k)# 初始化累积梯度平方和为,AdaGrad时要用到,防止除0异常self.G = np.ones(shape=(n, m, k), dtype=np.float64)self.log = Logistic()# 特征组合式的线性加权求和def phi(self, node_list):#  node_list: 一个样本,用链表存储x中的非0值z = 0.0for a in range(len(node_list)):node1 = node_list[a]j1 = node1.jf1 = node1.fv1 = node1.vfor b in range(a + 1, len(node_list)):node2 = node_list[b]j2 = node2.jf2 = node2.fv2 = node2.vw1 = self.w[j1, f2]w2 = self.w[j2, f1]z += np.dot(w1, w2) * v1 * v2  # 域感知的因子分解法FFMreturn z# 输入x,预测y的值def predict(self, node_list):# node_list: 用链表存储x中的非0值z = self.phi(node_list)y = self.log.decide_by_tanh(z)return y# 根据一个样本来更新模型参数def sgd(self, node_list, y):# node_list: 用链表存储x中的非0值。 y: 正样本1,负样本-1kappa = -y / (1 + math.exp(y * self.phi(node_list)))for a in range(len(node_list)):node1 = node_list[a]j1 = node1.jf1 = node1.fv1 = node1.vfor b in range(a + 1, len(node_list)):node2 = node_list[b]j2 = node2.jf2 = node2.fv2 = node2.vc = kappa * v1 * v2# self.w[j1,f2]和self.w[j2,f1]是向量,导致g_j1_f2和g_j2_f1也是向量g_j1_f2 = self.lambd * self.w[j1, f2] + c * self.w[j2, f1]g_j2_f1 = self.lambd * self.w[j2, f1] + c * self.w[j1, f2]# 计算各个维度上的梯度累积平方和self.G[j1, f2] += g_j1_f2 ** 2  # 所有G肯定是大于0的正数,因为初始化时G都为1self.G[j2, f1] += g_j2_f1 ** 2# AdaGradself.w[j1, f2] -= self.eta / np.sqrt(self.G[j1, f2]) * g_j1_f2  # sqrt(G)作为分母,所以G必须是大于0的正数self.w[j2, f1] -= self.eta / np.sqrt(self.G[j2, f1]) * g_j2_f1  # math.sqrt()只能接收一个数字作为参数,而numpy.sqrt()可以接收一个array作为参数,表示对array中的每个元素分别开方# 根据一堆样本训练模型def train(self, sample_generator, max_echo, max_r2):''':param sample_generator: 样本生成器,每次yield (node_list, y),node_list中存储的是x的非0值。通常x要事先做好归一化,即模长为1,这样精度会略微高一点:param max_echo: 最大迭代次数:param max_r2: 拟合系数r2达到阈值时即可终止学习:return:'''for itr in range(max_echo):print("echo", itr)y_sum = 0.0y_square_sum = 0.0err_square_sum = 0.0  # 误差平方和population = 0  # 样本总数for node_list, y in sample_generator:self.sgd(node_list, y)y = 0.0 if y == -1 else y  # 真实的y取值为{-1,1},而预测的y位于(0,1),计算拟合效果时需要进行统一y_hat = self.predict(node_list)y_sum += yy_square_sum += y ** 2err_square_sum += (y - y_hat) ** 2population += 1var_y = y_square_sum - y_sum * y_sum / population  # y的方差r2 = 1 - err_square_sum / var_yprint("r2=",r2)if r2 > max_r2:  # r2值越大说明拟合得越好print('r2 have reach', r2)break# 序列化模型,保存到文件def save_model(self, outfile):np.save(outfile, self.w)# 从文件中加载模型def load_model(self, infile):self.w = np.load(infile)

FM、FFM应用

库的安装教程https://blog.csdn.net/luanpeng825485697/article/details/77816740

pyfm库下载地址,在网址中搜索pyfm:https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyfm

fastfm下载地址:https://github.com/ibayer/fastFM

ffm库的下载地址:https://github.com/alexeygrigorev/libffm-python

参考:https://blog.csdn.net/jediael_lu/article/details/77772565
https://tech.meituan.com/deep-understanding-of-ffm-principles-and-practices.html

https://www.cnblogs.com/zhangchaoyang/articles/8410719.html

python机器学习案例系列教程——CTR/CVR中的FM、FFM算法相关推荐

  1. python机器学习案例系列教程——GBDT构建新特征

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 GBDT的算法参考:https://blog.csdn.net/luanpeng825485697/article/details/7 ...

  2. python机器学习案例系列教程——集成学习(Bagging、Boosting、随机森林RF、AdaBoost、GBDT、xgboost)

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 可以通过聚集多个分类器的预测结果提高分类器的分类准确率,这一方法称为集成(Ensemble)学习或分类器组合(Classifier C ...

  3. python机器学习案例系列教程——决策树(ID3、C4.5、CART)

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 决策树简介 决策树算是最好理解的分类器了.决策树就是一个多层if-else函数,就是对对象属性进行多层if-else判断,获取目标属性 ...

  4. python机器学习案例系列教程——推荐系统

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 主流的推荐系统算法大致分为两类: 基于用户行为数据的协同过滤算法 基于内容数据的过滤算法 大致而言,基于内容数据的算法适用于cold ...

  5. python机器学习案例系列教程——GBDT算法、XGBOOST算法

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 GBDT概述 GBDT也是集成学习Boosting家族的成员,但是却和传统的Adaboost有很大的不同.回顾下Adaboost,我们 ...

  6. python机器学习案例系列教程——关联分析(Apriori、FP-growth)

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 关联分析的基本概念 关联分析(Association Analysis):在大规模数据集中寻找有趣的关系. 频繁项集(Frequent ...

  7. python机器学习案例系列教程——极大似然估计、EM算法

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 极大似然 极大似然(Maximum Likelihood)估计为用于已知模型的参数估计的统计学方法. 也就是求使得似然函数最大的代估参 ...

  8. python机器学习案例系列教程——k均值聚类、k中心点聚类

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 上一篇我们学习了层次聚类.层次聚类只是迭代的把最相近的两个聚类匹配起来.并没有给出能给出多少的分组.今天我们来研究一个K均值聚类.就是 ...

  9. python机器学习案例系列教程——逻辑分类/逻辑回归LR/一般线性回归(softmax回归)

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 线性函数.线性回归 参考:http://blog.csdn.net/luanpeng825485697/article/details ...

最新文章

  1. SourceTree 教程文档(了解界面)
  2. .NET Framework/.NET Compact Framework/.NET Micro Framework功能集比较
  3. php 二维数组 根据某个字段排序
  4. 关于Extjs gridpanel设置autoHeight:true时,横向滚动条的问题
  5. 只有20行Javascript代码!手把手教你写一个页面模板引擎
  6. Linux下ar指令与静态库转动态库方法
  7. 数据结构(三):非线性逻辑结构-树
  8. ironpython使用方法_IronPython连接MySQL的方法步骤
  9. Internet Download Manager_IDM 6.38 B17-91apps.cn授权双版
  10. 【人工智能】人工智能是20世纪50年代中期兴起的一门新兴边缘科学
  11. C语言回溯算法求幂集,回溯法与树的遍历 - 求幂集
  12. kali Linux 系统安装教程
  13. Android 界面设计 简约个人求职简历表格
  14. Android 自定义ViewGroup 设置wrap_content无效解决办法。
  15. 计算机老师写对联给新人,写给教师结婚幽默对联【值得收藏】
  16. python的def语句_关于语法:python def函数:如何指定函数的结尾?
  17. 飞机大战的常见Bug
  18. 珍重了,我亲爱的朋友们
  19. GEE引擎自定义进度条和自定义属性的脚本展示
  20. (附源码)springboot大学医学生毕业实习分配系统 毕业设计212 002

热门文章

  1. IBM - 开拓语音识别 - 概述 - 中国
  2. php checkbox表单提交,HTML表单Checkbox的值如何正确提交到PHP后台?,需要技巧
  3. Git创建分支并上传~满满的干货哦
  4. ES6字符串的扩展方法~超详细哦
  5. el-dropdown 事件
  6. 用c++实现一个插入,删除和随机访问都是O(1)的容器(剑指||30)
  7. ffmpeg系列-解复用流程解析
  8. 100行代码实现最简单的基于FFMPEG+SDL的视频播放器
  9. mysql5.6 install_mysql5.6安装
  10. layer 弹出框(iframe层)父子页面传值