前言
很久没更新过东西了,想记录一下学习过程,开一个CTR预估的系列,希望可以一直坚持更新吧~
FM背景
- LR的缺点:
- 线性模型无法学习特征间的交互信息
- 特征组合对于实际的效果显著
- 人为进行特征交叉由于用户行为的稀疏导致参数无法有效更新
- 特征组合的有效性
- 举个简单的例子,在预测一个用户是否会对剃须刀感兴趣时,性别男+剃须刀的组合特征相应权重可能更大,意味着二者共同出现时感兴趣的可能性更大,而性别女+剃须刀的组合特征对应的权重可能就会比较小,也就是说女性对于剃须刀的兴趣程度可能相对不高,这就和我们人的思维方式比较类似。
- 而线性模型如线性回归或者逻辑回归,由于模型自身的原因只能分别计算各类特征的结果值然后求和,而无法学习到如男性+剃须刀二者共同出现时会大大提高用户的感兴趣程度。
- FM解决的问题:
- 稀疏数据下的特征组合参数难以训练的问题
- 下面将通过实际的例子来介绍FM算法
介绍
- 这里以美团文章中的图来解释说明,美团文章地址(https://tech.meituan.com/2016/03/03/deep-understanding-of-ffm-principles-and-practices.html)
- 假设目前我们的数据如下格式
图中共有三个特征,分别为Country,Day,Ad_type,三个特征都是离散类型,那么进行onehot编码以后形成的矩阵为:
在进行onehot以后特征的维度编程了7维。
根据我们的需求可以简单对LR的线性部分的计算公式进行更改如下,即为交叉特征加上权重进行学习,由于我们的维度共有7维,那么需要学习的交叉特征的权重数量为
w
i
j
w_{ij}
wij的个数则为
C
7
2
=
21
C_7^2=21
C72=21个,修改以后的计算公式如下:
y
(
x
)
=
w
0
+
∑
i
=
0
N
w
i
x
i
+
∑
i
=
1
N
−
1
∑
j
=
i
+
1
N
w
i
j
x
i
x
j
y(x)=w_0+\sum_{i=0}^Nw_ix_i+\sum_{i=1}^{N-1} \sum_{j=i+1}^N w_{ij}{x_ix_j}
y(x)=w0+i=0∑Nwixi+i=1∑N−1j=i+1∑Nwijxixj
公式看似非常美好,但是存在一个致命的问题就是
w
i
j
w_{ij}
wij只有在
x
i
x_i
xi和
x
j
x_j
xj都不为0时才能通过梯度下降进行更新(其中之一为0时将导致梯度为0),而通常用户的数据非常稀疏导致
x
i
x_i
xi和
x
j
x_j
xj都不为0的情况很少,从而使得特征参数无法训练。
- FM的解决方案
FM中的做法利用了矩阵分解的思想,由于 w i j = w j i w_{ij}=w_{ji} wij=wji,所以交叉项的矩阵W是一个实对称矩阵,而对于任意的实对称矩阵一定可以分解为特征向量矩阵与特征值对角矩阵的乘积,如下:
当然这里并不是直接对W进行这种方式的分解,这是因为W值我们还尚未求到,这里采用的方式是将W矩阵近似分解为两个矩阵的乘积,示意图如下:
由上图可以发现,rating矩阵的维度是n*m,将其分解为两个矩阵的近似乘积表示,其中user矩阵为n*2,movies矩阵维度为2*m,二者进行矩阵乘后得到的维度即是n*m。
这种思路用在我们这里对W进行矩阵分解,就可以想象成对于每个
w
i
j
w_{ij}
wij都可以表示为
v
i
∗
v
j
v_i*v_j
vi∗vj,其中
v
i
v_i
vi是第i个特征对应的向量,
v
j
v_j
vj是第j个特征对应的向量,分解后的图为:
在进行了这种表达后,FM的线性部分的计算公式就变成了:
y
(
x
)
=
w
0
+
∑
i
=
1
n
−
1
w
i
x
i
+
∑
i
=
1
n
−
1
∑
j
=
i
+
1
n
⟨
v
i
,
v
j
⟩
x
i
x
j
y(x)=w0+\sum_{i=1}^{n-1}w_ix_i+\sum_{i=1}^{n-1}\sum_{j=i+1}^n⟨v_i,v_j⟩x_ix_j
y(x)=w0+i=1∑n−1wixi+i=1∑n−1j=i+1∑n⟨vi,vj⟩xixj
其中,
<
v
i
,
v
j
>
<v_i,v_j>
<vi,vj>代表两个向量的内积,对该式进行化简:
从上面的式子中可以发现,希望对 v i , f v_{i,f} vi,f进行更新时,只需要 x i x_i xi不为0即可,从而可以有效地解决数据稀疏时特征交叉的组合问题,梯度更新的公式如下,大家也可以手动推导一下。
- 上面就是FM整个的过程内容了,下面以TensorFlow简单运行一下FM,由于只是简单验证下内容,所以只是用了id信息进行训练,没有加入过多的内容,大家可以根据实际加入更多的特征进行训练。
TensorFlow训练
1.定义需要的变量信息,输入使用placeholder占位,变量利用随机正态初始化,这里我的特征进行过相应的处理,所以此处不另外添加。
rows,cols = x_train.shape
# latent factors
k = 10
X = tf.placeholder(tf.float32,shape=[None,cols])
y = tf.placeholder(tf.float32,shape=[None,1])
# weights and bias
W = tf.Variable(tf.random_normal([cols,1],0.,0.01))
b = tf.Variable(tf.zeros([1]))
# 交叉的隐向量
V = tf.Variable(tf.random_normal([cols,k],0,0.01))
# 预测y
y_hat = tf.Variable(tf.zeros([rows,1]))
2.定义y_hat计算
y_hat = tf.add(b,tf.matmul(X,W))+1/2*tf.reduce_sum(
tf.square(tf.matmul(X,V))-tf.matmul(tf.square(X),tf.square(V)),axis=1,keep_dims=True)
3.定义训练的损失函数
lambda_w = tf.constant(0.001,tf.float32)
lambda_v = tf.constant(0.001,tf.float32)
reg_value = tf.add(tf.multiply(lambda_w,tf.reduce_mean(tf.pow(W,2))),tf.multiply(lambda_v,tf.reduce_mean(tf.pow(V,2))))
error = tf.reduce_mean(tf.square(tf.subtract(y, y_hat)))
Loss = error+reg_value
4.定义优化器,这里使用梯度下降
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01).minimize(Loss)
5.训练模型
# train model
epochs = 5
batch_size = 512
init = tf.global_variables_initializer()
with tf.Seesion() as sess:
sess.run(init)
for epoch in range(epochs):
print('this is the {} epoch'.format(epoch))
# 每轮训练打乱顺序
random_perm = np.random.permutation(x_train.shape[0])
for aX,aY in get_batch(x_train[random_perm],y_train[random_perm].reshape(-1,1),batch_size):
sess.run(optimizer,feed_dict={X:aX,y:aY})