很久没更新过东西了,想记录一下学习过程,开一个CTR预估的系列,希望可以一直坚持更新吧~
图中共有三个特征,分别为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信息进行训练,没有加入过多的内容,大家可以根据实际加入更多的特征进行训练。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_value4.定义优化器,这里使用梯度下降
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})