Factorization Machine

文章来自:深入FFM原理与实践

【动机】

特征的交叉是有用的,于是想到构造二次项特征,对应着如下的多项式模型
y(x)=w0+i=1nwixi+i=1nj=i+1nwijxixj 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

参数包括: w0 w 0 w1w2wn [ w 1 w 2 ⋮ w n ] W=w12w13w23w1nw2nwn1,n W = [ − w 12 w 13 ⋯ w 1 n − w 23 ⋯ w 2 n − ⋯ ⋮ − w n − 1 , n − ]
其中矩阵 W W 包含(n1)+(n2)++2+1=n(n1)2个参数

对于参数 wij w i j ,只有当特征 xi x i xj x j 都非 0 0 时,才产生loss,因此wij需要大量 xi x i xj x j 都非0的样本才能进行训练

然而在实际场景中,特征向量 x x 往往是高维且稀疏的(由于对cat型变量作one-hot编码),满足“ xi x i xj x j 都非零”的样本将会非常少

训练样本的不足,很容易导致参数 wij w i j 不准确,最终将严重影响模型的性能

【FM思想】

FM借鉴了协同过滤中将rating矩阵分解为user矩阵和item矩阵的方法

在本问题中,将矩阵 W W 分解为两个相同的矩阵V,即 W=VTV W = V T V ,其中 V V 是一个n×k的矩阵, k k 是隐向量的维度,通常kn,于是参数个数由 n(n1)2 n ( n − 1 ) 2 个下降到 kn k n

【FM模型公式】

y(x)=w0+i=1nwixi+i=1nj=i+1nvi,vjxixj 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
参数包括: w0 w 0 w1w2wn [ w 1 w 2 ⋮ w n ] Vn×k=v1v2vn=v1,1v2,1vn,1v1,2v2,2vn,2v1,kv2,kvn,k V n × k = [ − v 1 − − v 2 − ⋮ − v n − ] = [ v 1 , 1 v 1 , 2 ⋯ v 1 , k v 2 , 1 v 2 , 2 ⋯ v 2 , k ⋮ ⋮ ⋱ ⋮ v n , 1 v n , 2 ⋯ v n , k ]

【二次项化简】

i=1nj=i+1nvi,vjxixj=12f=1k(i=1nvi,fxi)2i=1nv2i,fx2i ∑ 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 ]

左式外层的二重求和复杂度为 O(n2) O ( n 2 ) ,内层计算向量点乘复杂度为 O(k) O ( k ) ,于是整个式子的复杂度为 O(kn2) O ( k n 2 )

右式是一个二重求和,内外的复杂度分别为 O(n) O ( n ) O(k) O ( k ) ,故整个式子的复杂度为 O(kn) O ( k n )

综上所述,二次项经过化简,计算复杂度由 O(kn2) O ( k n 2 ) 降为 O(kn) O ( k n )

【二次项化简的推导】

假设 n=4 n = 4 k=3 k = 3

(1) 展开左式的二重求和符号

(2) 展开向量点乘 vi,vj ⟨ v i , v j ⟩

(3) 按照分量 f=1,2,3 f = 1 , 2 , 3 分类,拆分为 3 3 个子表

(4) 另一方面,构造如下式子,展开之后得到下表

(4) 将平方项减去之后乘上1/2

我们得到了(3)中完全相同的表,于是推导结束

【参数梯度】

为了便于说明,仍然假设 n=4 n = 4 k=3 k = 3 ,对于某个 f f ,有
12[(i=14vi,fxi)2i=14vi,f2xi2]=12[(v1,fx1+v2,fx2+v3,fx3+v4,fx4)2(v1,f2x12+v2,f2x22+v3,f2x32+v4,f2x42)]

上式对 vi,f v i , f 求导,得

yvi,f=12[2(v1,fx1+v2,fx2+v3,fx3+v4,fx4)xi2vi,fx2i]=(v1,fx1+v2,fx2+v3,fx3+v4,fx4)xivi,fx2i=xij=14vj,fxjvi,fx2i ∂ y ∂ v i , f = 1 2 [ 2 ( v 1 , f x 1 + v 2 , f x 2 + v 3 , f x 3 + v 4 , f x 4 ) x i − 2 v i , f x i 2 ] = ( v 1 , f x 1 + v 2 , f x 2 + v 3 , f x 3 + v 4 , f x 4 ) x i − v i , f x i 2 = x i ∑ j = 1 4 v j , f x j − v i , f x i 2

FM模型各个参数的梯度如下

yθ=1xixij=1nvj,fxjvi,fx2iif θ is w0if θ is wiif θ is vi,f ∂ y ∂ θ = { 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

对于某个 f f j=1nvj,fxj求完之后可以反复使用,求和的复杂度为 O(n) O ( n ) ,因此整个模型的训练复杂度为 O(kn) O ( k n )

【FM模型缺点】
本质上为线性模型,没有考虑Field-aware

【loss function及梯度代码】

import numpy as np

seed = 0
np.random.seed( seed )



n, k, batch_size = 4, 3, 5

V = np.random.rand( n, k )
x = np.random.rand( n )
X = np.tile( x, (batch_size, 1) )

【非向量化实现,求1个样本x的loss,复杂度为 O(kn2) O ( k n 2 ) 的计算方法】

loss = 0
for i in range(n):
    for j in range(i+1, n):
        v_i, v_j = V[i, :], V[j, :]
        loss += np.dot( v_i, v_j ) * x[i] * x[j]

print( 'loss =', loss )

【非向量化实现,求1个样本x的loss,复杂度为 O(kn) O ( k n ) 的计算方法】

loss = 0
for f in range(k):

    term1, term2 = 0, 0

    for i in range(n):
        term1 += V[i, f] * x[i]
        term2 += V[i, f] ** 2 * x[i] ** 2

    loss += term1 ** 2 - term2

loss /= 2
print( 'loss =', loss )

【向量化实现,求batch_size个样本X的loss】

loss = 1/2 * np.sum( np.dot( X, V ) ** 2 - np.dot( X ** 2, V ** 2 ), axis=1 )
loss = np.mean( loss )
print( 'loss =', loss )

【非向量化实现,求1个样本x关于V的梯度,复杂度 O(kn) O ( k n )

grad_V = np.zeros_like(V)

for f in range(k):

    temp = 0
    for j in range(n):
        temp += V[j, f] * x[j]

    for i in range(n):
        grad_V[i, f] = x[i] * temp - V[i, f] * x[i]**2

print( grad_V )

【向量化实现,求1个样本x关于V的梯度,复杂度O(kn)】
temp = np.dot(x, V)
term1 = np.dot( np.expand_dims(x, axis=1), np.expand_dims(temp, axis=0) )

V * np.expand_dims(x**2, axis=1)使用了boardcast

V.shape=(n, k) np.expand_dims(x**2, axis=1).shape=(n, 1)

term2 = V * np.expand_dims(x**2, axis=1)

grad_V = term1 - term2
print( grad_V )

向量化实现,求batch_size个样本X关于V的梯度,复杂度O(kn)

term1 = np.dot( X.T, np.dot(X, V) )
term2 = V * np.dot( (X**2).T, np.ones( (batch_size, k) ) )
grad_V = term1 - term2
print( grad_V / batch_size )

梯度检查

def compute_loss( V, X ):
loss = 1/2 * np.sum( np.dot( X, V ) * 2 - np.dot( X * 2, V ** 2 ), axis=1 )
loss = np.mean( loss )
return loss

grad_V = np.zeros_like(V)
epsilon = 1e-4

for i in range( V.shape[0] ):
for j in range( V.shape[1] ):

    epsilon_vec = np.zeros_like(V)
    epsilon_vec[i, j] += epsilon

    grad_V[i, j] = ( compute_loss( V+epsilon_vec, X ) - compute_loss( V-epsilon_vec, X ) ) / ( 2 * epsilon )

print( grad_V )
【题外话】
A = np.random.rand(n, batch_size)

对一个矩阵按行求和,相当于右乘一个全为1的列向量

temp1 = np.sum( A, axis=1, keepdims=True )
temp2 = np.dot( A, np.ones( (batch_size, 1) ) )
print( temp1 )
print( temp2 )

对一个列向量做水平方向tile,相当于右乘一个全为1的行向量

temp1 = np.tile( temp1, (1, k) )
temp2 = np.dot( temp2, np.ones( (1, k) ) )
print( temp1 )
print( temp2 )

将上述两步合并起来,直接对矩阵右乘一个全为1的矩阵

temp = np.dot( A, np.ones( (batch_size, k) ) )
print( temp )

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值