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

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

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基本原理

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

y(x)=w0+i=1nwixi+i=1nj=i+1nwijxixj(1) (1) y ( x ) = w 0 + ∑ i = 1 n w i x i + ∑ i = 1 n ∑ j = i + 1 n w i j x i x j

其中, n n 代表样本的特征数量, xi x i 是第 i i 个特征的值, w0 w 0 wi w i wij w i j 是模型参数。

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

系数矩阵分解

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

对于对称矩阵W,

W=ω11ω21ωn1ω12ω22ωn2.........ω1nω2nωnnn×n W = ( ω 11 ω 12 . . . ω 1 n ω 21 ω 22 . . . ω 2 n ⋮ ⋮ ⋱ ⋮ ω n 1 ω n 2 . . . ω n n ) n × n

由于直接求解W不方便,因此我们引入隐变量V:

V=v11v21vn1v12v22vn2.........v1kv2kvnkn×k=VT1VT2VTn V = ( v 11 v 12 . . . v 1 k v 21 v 22 . . . v 2 k ⋮ ⋮ ⋱ ⋮ v n 1 v n 2 . . . v n k ) n × k = ( V 1 T V 2 T ⋯ V n T )

满足

VVT=W V V T = W

V V 的第 j j vj v j 便是第 j j 维特征的隐向量。换句话说,每个参数 wij=vi,vj w i j = ⟨ v i , v j ⟩ ,这就是FM模型的核心思想。因此,FM的模型方程为(本文不讨论FM的高阶形式)

y(x)=w0+i=1nwixi+i=1nj=i+1n<vi,vj>xixj(2) (2) y ( x ) = w 0 + ∑ i = 1 n w i x i + ∑ i = 1 n ∑ j = i + 1 n < v i , v j > x i x j

参数个数

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

预测时间复杂度

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

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

i=1nj=i+1nvi,vjxixj=12f=1k(i=1nvi,fxi)2i=1nv2i,fx2i(3) (3) ∑ i = 1 n ∑ j = i + 1 n ⟨ v i , v j ⟩ x i x j = 1 2 ∑ f = 1 k ( ( ∑ i = 1 n v i , f x i ) 2 − ∑ i = 1 n v i , f 2 x i 2 )

梯度下降法

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

θy(x)=1,xi,xinj=1vj,fxjvi,fx2i,ifθisw0ifθiswiifθisvi,f ∂ ∂ θ y ( x ) = { 1 , if θ is w 0 x i , if θ is w i x i ∑ j = 1 n v j , f x j − v i , f x i 2 , if θ is v i , f

其中, vj,f v j , f 是隐向量 vj v j 的第 f f 个元素。由于 nj=1vj,fxj ∑ j = 1 n v j , f x j 只与 f f 有关,而与 i i 无关,在每次迭代过程中,只需计算一次所有 f f nj=1vj,fxj ∑ j = 1 n v j , f x j ,就能够方便地得到所有 vi,f v i , f 的梯度。显然,计算所有 f f nj=1vj,fxj ∑ j = 1 n v j , f x j 的复杂度是 O(kn) O ( k n ) ;已知 nj=1vj,fxj ∑ j = 1 n v j , f x j 时,计算每个参数梯度的复杂度是 O(1) O ( 1 ) ;得到梯度后,更新每个参数的复杂度是 O(1) O ( 1 ) ;模型参数一共有 nk+n+1 n k + n + 1 个。因此,FM参数训练的复杂度也是 O(kn) O ( k n ) 。综上可知,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中,每一维特征 xi x i ,针对其它特征的每一种field fj f j 其中 fj f j 表示第j维特征所属的field),都会学习一个隐向量 vi,fj v i , f j 。因此,隐向量不仅与特征相关,也与field相关。也就是说,“Day=26/11/15”这个特征与“国家”特征和“广告类型”特征进行关联的时候使用不同的隐向量,这与“国家”和“广告类型”的内在差异相符

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

y(x)=w0+i=1nwixi+i=1nj=i+1n<vi,fj,vj,fi>xixj(4) (4) y ( x ) = w 0 + ∑ i = 1 n w i x i + ∑ i = 1 n ∑ j = i + 1 n < v i , f j , v j , f i > x i x j

其中, fj f j 是第 j j 个特征所属的field。如果隐向量的长度为 k k ,那么FFM的二次参数有 nfk n f k 个,远多于FM模型的 nk n k 个。此外,由于隐向量与field相关,FFM二次项并不能够化简,其预测复杂度是 O(kn2) O ( k n 2 )

下面以一个例子简单说明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项,如下图所示。

v1,2,v2,111+v1,3,v3,111+v1,3,v4,111+v1,4,v5,119.99+v2,3,v3,211+v2,3,v4,211+v2,4,v5,219.99+v3,3,v4,311+v3,4,v5,319.99+v4,4,v5,319.99 ⟨ v 1 , 2 , v 2 , 1 ⟩ ⋅ 1 ⋅ 1 + ⟨ v 1 , 3 , v 3 , 1 ⟩ ⋅ 1 ⋅ 1 + ⟨ v 1 , 3 , v 4 , 1 ⟩ ⋅ 1 ⋅ 1 + ⟨ v 1 , 4 , v 5 , 1 ⟩ ⋅ 1 ⋅ 9.99 + ⟨ v 2 , 3 , v 3 , 2 ⟩ ⋅ 1 ⋅ 1 + ⟨ v 2 , 3 , v 4 , 2 ⟩ ⋅ 1 ⋅ 1 + ⟨ v 2 , 4 , v 5 , 2 ⟩ ⋅ 1 ⋅ 9.99 + ⟨ v 3 , 3 , v 4 , 3 ⟩ ⋅ 1 ⋅ 1 + ⟨ v 3 , 4 , v 5 , 3 ⟩ ⋅ 1 ⋅ 9.99 + ⟨ v 4 , 4 , v 5 , 3 ⟩ ⋅ 1 ⋅ 9.99

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

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

ϕ(V,x)=i=1nj=i+1n<vi,fj,vj,fi>xixj=i=1nj=i+1n(vTi,fjvj,fi)xixj ϕ ( V , x ) = ∑ i = 1 n ∑ j = i + 1 n < v i , f j , v j , f i > x i x j = ∑ i = 1 n ∑ j = i + 1 n ( v i , f j T v j , f i ) x i x j

最优化问题

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

minvi=1Llog(1+exp{yiϕ(v,xi)})+λ2v2 min v ∑ i = 1 L log ⁡ ( 1 + exp ⁡ { − y i ϕ ( v , x i ) } ) + λ 2 ‖ v ‖ 2

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

FFM代码实现

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

m m :域的个数

k k :隐向量的维度

j j :在特征中的下标

f f :在域中的下标

d d :在隐向量中的下标

l l :样本的总数

粗体字母表示向量或矩阵

特征组合

最基本的线性加权

ϕLM(w,x)=i=1nwixi ϕ L M ( w , x ) = ∑ i = 1 n w i x i

任意特征两两组合

ϕpoly2(w,x)=j1=1nj2=j1+1nwj1,j2xj1xj2 ϕ p o l y 2 ( w , x ) = ∑ j 1 = 1 n ∑ j 2 = j 1 + 1 n w j 1 , j 2 x j 1 x j 2

w w 是一个对称方阵,即 wj1,j2=wj2,j1 w j 1 , j 2 = w j 2 , j 1 ,可以用矩阵分解法来拟合 w w

wj1,j2=vj1vj2=vj2vj1=wj2,j1 w j 1 , j 2 = v j 1 ⋅ v j 2 = v j 2 ⋅ v j 1 = w j 2 , j 1

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

因子分解法FM

ϕFM(w,x)=j1=1nj2=j1+1nwj1wj2xj1xj2 ϕ F M ( w , x ) = ∑ j 1 = 1 n ∑ j 2 = j 1 + 1 n w j 1 ⋅ w j 2 x j 1 x j 2

这里的 wj w j 相当于上面的 vj v j

域感知的因子分解法FFM

ϕFFM(w,x)=j1=1nj2=j1+1nwj1,f2wj2,f1xj1xj2 ϕ F F M ( w , x ) = ∑ j 1 = 1 n ∑ j 2 = j 1 + 1 n w j 1 , f 2 ⋅ w j 2 , f 1 x j 1 x j 2

在FM中w是规模为 n×kFM n × k F M 的二维矩阵,而在FFM中 w w 是规模为 n×m×kFFM n × m × k F F M 的三维矩阵, kFFMkFM k F F M ≪ k F M

逻辑回归二分类

决策函数

y^=11+exp(ϕFFM(w,x)) y ^ = 1 1 + e x p ( − ϕ F F M ( w , x ) )

带L2正则的目标函数

minwλ2w22+i=1llog(1+exp(yiϕFFM(w,xi))) min w λ 2 ∥ w ∥ 2 2 + ∑ i = 1 l l o g ( 1 + e x p ( − y i ϕ F F M ( w , x i ) ) )

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

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

minwλ2w22+log(1+exp(yϕFFM(w,x))) min w λ 2 ∥ w ∥ 2 2 + l o g ( 1 + e x p ( − y ϕ F F M ( w , x ) ) )

梯度

gj1,f2=λwj1,f2+κwj2,f1xj1xj2 g j 1 , f 2 = λ ⋅ w j 1 , f 2 + κ ⋅ w j 2 , f 1 x j 1 x j 2

gj2,f1=λwj2,f1+κwj1,f2xj1xj2 g j 2 , f 1 = λ ⋅ w j 2 , f 1 + κ ⋅ w j 1 , f 2 x j 1 x j 2

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

其中

κ=log(1+exp(yϕFFM(w,x)))ϕFFM(w,x)=y1+exp(yϕFFM(w,x)) κ = ∂ l o g ( 1 + e x p ( − y ϕ F F M ( w , x ) ) ) ∂ ϕ F F M ( w , x ) = − y 1 + e x p ( y ϕ F F M ( w , x ) )

AdaGrad更新w

(Gj1,f2)d(Gj1,f2)d+(gj1,f2)2d ( G j 1 , f 2 ) d ← ( G j 1 , f 2 ) d + ( g j 1 , f 2 ) d 2

(Gj2,f1)d(Gj2,f1)d+(gj2,f1)2d ( G j 2 , f 1 ) d ← ( G j 2 , f 1 ) d + ( g j 2 , f 1 ) d 2

(wj1,f2)d(wj1,f2)dη(Gj1,f2)d(gj1,f2)d ( w j 1 , f 2 ) d ← ( w j 1 , f 2 ) d − η ( G j 1 , f 2 ) d ( g j 1 , f 2 ) d

(wj2,f1)d(wj2,f1)dη(Gj2,f1)d(gj2,f1)d ( w j 2 , f 1 ) d ← ( w j 2 , f 1 ) d − η ( G j 2 , f 1 ) d ( g j 2 , f 1 ) d

初始化 Gd=1 G d = 1 ,这样在计算 ηGd η G d 时既可以防止分母为0,又可以避免该项太大或太小。

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

初始的 w w 可以从均匀分布中抽样 wU(0,1k) w ∼ U ( 0 , 1 k )
实现发现将每个 x x 归一化,即模长为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_loss

X, 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 np

np.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 = j
        self.f = f
        self.v = v


class FFM(object):
    def __init__(self, m, n, k, eta, lambd):
        # m 域个数,n特征个数,k隐变量维度,eta学习速率,lambd正则系数
        self.m = m
        self.n = n
        self.k = k
        # 超参数
        self.eta = eta
        self.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.0
        for a in range(len(node_list)):
            node1 = node_list[a]
            j1 = node1.j
            f1 = node1.f
            v1 = node1.v
            for b in range(a + 1, len(node_list)):
                node2 = node_list[b]
                j2 = node2.j
                f2 = node2.f
                v2 = node2.v
                w1 = self.w[j1, f2]
                w2 = self.w[j2, f1]
                z += np.dot(w1, w2) * v1 * v2  # 域感知的因子分解法FFM
        return 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,负样本-1
        kappa = -y / (1 + math.exp(y * self.phi(node_list)))
        for a in range(len(node_list)):
            node1 = node_list[a]
            j1 = node1.j
            f1 = node1.f
            v1 = node1.v
            for b in range(a + 1, len(node_list)):
                node2 = node_list[b]
                j2 = node2.j
                f2 = node2.f
                v2 = node2.v
                c = 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都为1
                self.G[j2, f1] += g_j2_f1 ** 2
                # AdaGrad
                self.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.0
            y_square_sum = 0.0
            err_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 += y
                y_square_sum += y ** 2
                err_square_sum += (y - y_hat) ** 2
                population += 1
            var_y = y_square_sum - y_sum * y_sum / population  # y的方差
            r2 = 1 - err_square_sum / var_y
            print("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算法)