FM算法

目录

1.FM背景与简介

1.1 稀疏数据

1.2为什么进行特征组合

1.3如何组合特征

1.4二次项参数求解方法

1.5公式推导

1.6关于隐向量

2:FM代码实现

3:xlearn流程以及抽取embeding向量

3.1:数据处理

3.2:训练模型

3.3抽取向量


1.FM背景与简介

FM目的主要是为了解决稀疏数据下的特征组合问题。2010年,日本大阪大学(Osaka University)的 Steffen Rendle 在矩阵分解(MF)、SVD++[2]、PITF[3]、FPMC[4] 等基础之上,归纳出针对高维稀疏数据的因子机(Factorization Machine, FM)模型[13]。因子机模型可以将上述模型全部纳入一个统一的框架进行分析[1]。目前,FM被广泛的应用于广告预估模型中,相比LR而言,效果强了不少。

1.1 稀疏数据

高维的稀疏矩阵是工程中常见的问题,比如下图:

图中每一个样本对应多个属性,分别是User,Movie,Other Movies rated,Time,Last Movie rated,由于这些属性都是categorical类型,所以一般进行One-hot编码转换为数值类型,但是由于每个属性都有多个离散的取值,所以One-hot编码之后样本空间相比原来变大了许多,而特征矩阵也会变得非常稀疏。假设有10000部电影,有10000个用户,单看前两条,每个样本的特征维度就是两万维,但是每个特征中只有两维取值不为0,非常稀疏。在CTR/CVR预测时,One-Hot编码常会导致样本的稀疏性,样本的稀疏性是实际问题中不可避免的挑战。

1.2为什么进行特征组合

下面以一个示例引入FM模型。假设一个广告分类的问题,根据用户和广告位相关的特征,预测用户是否点击了广告。源数据如下:

“Clicked?”是label,Country、Day、Ad_type是特征。由于三种特征都是categorical类型的,需要经过独热编码(One-Hot Encoding)转换成数值型特征。

由上表可以看出,经过One-Hot编码之后,大部分样本数据特征是比较稀疏的。上面的样例中,每个样本有7维特征,但平均仅有3维特征具有非零值。

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

 

1.3如何组合特征

SVM就曾通过多项式核函数来实现特征的交叉。实际上,多项式是构建组合特征的一种非常直观的模型。我们先看一下二阶多项式的建模,只有两个特征都是非零的时候组合特征才有意义:

从公式来看,模型前半部分就是普通的LR线性组合,后半部分的交叉项即:特征的组合。单从模型表达能力上来看,FM的表达能力是强于LR的,至少不会比LR弱,当交叉项参数全为0时退化为普通的LR模型。

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

1.4二次项参数求解方法

矩阵分解提供了一种解决思路。在model-based的协同过滤中,一个rating矩阵可以分解为user矩阵和item矩阵,每个user和item都可以采用一个隐向量表示。比如在下图中的例子中,我们把每个user表示成一个二维向量,同时把每个item表示成一个二维向量,两个向量的点积就是矩阵中user对item的打分。

preview

任意的 N×N 实对称矩阵都有 N 个线性无关的特征向量。并且这些特征向量都可以正交单位化而得到一组正交且模为 1 的向量。故实对称矩阵 A 可被分解成:

1.5公式推导

引入辅助向量V之前,计算复杂度为O(n2),引入V之后,为O(kn),通过交叉项求解,我们知道,计算复杂度为O(kn)。一般来说k远小于n。

之后就可以用梯度下降法求解了,FM模型的梯度为:

1.6关于隐向量

这里的 vi 是 xi 特征的低纬稠密表达,实际中隐向量的长度通常远小于特征维度N,在实际的CTR场景中,数据都是很稀疏的category特征,通常表示成离散的one-hot形式,这种编码方式,使得one-hot vector非常长,而且很稀疏,同时特征总量也骤然增加,达到千万级甚至亿级别都是有可能的,而实际上的category特征数目可能只有几百维。FM学到的隐向量可以看做是特征的一种embedding表示,把离散特征转化为Dense Feature,这种Dense Feature还可以后续和DNN来结合,作为DNN的输入,事实上FNN的CTR也是这个思路来做的。

2:FM代码实现

def model():
    x_train, x_test, y_train, y_test = load_data()
    n, onehot_dim = x_train.shape
    embeding_size = 8
    w0 = tf.Variable(tf.zeros([1]))
    w = tf.Variable(tf.zeros([onehot_dim]))
    X = tf.placeholder(tf.float32, [None, onehot_dim], name="liner_para")
    Y = tf.placeholder(tf.float32, [None, 1], name='output')
    V = tf.Variable(tf.random_normal([onehot_dim, embeding_size], mean=0, stddev=0.01, seed=1), name="para_martix")
    #线性和
    liner_sum = tf.add(w0, tf.reduce_sum(tf.multiply(w, X), keep_dims=True, axis=1))
    pair_sum = 0.5*tf.reduce_sum(tf.subtract(tf.pow(tf.matmul(X, V), 2), tf.matmul(tf.pow(X, 2), tf.pow(V, 2))),axis=1,keep_dims=True)
    #交叉和
    y_hat=tf.add(liner_sum,pair_sum)
    #正则化部分
    lambda_w = tf.constant(0.001, name='lambda_w')
    lambda_v = tf.constant(0.001, name='lambda_v')
    l2_norm = tf.reduce_sum(
        tf.add(
            tf.multiply(lambda_w, tf.pow(w, 2)),
            tf.multiply(lambda_v, tf.pow(tf.transpose(V), 2))
        )
    )
    #损失函数
    error = tf.reduce_mean(tf.square(Y - y_hat))
    loss = tf.add(error, l2_norm)
    train_op = tf.train.GradientDescentOptimizer(learning_rate=0.01).minimize(loss)
    epochs = 10
    batch_size = 1000
    init = tf.global_variables_initializer()
    with tf.Session() as sess:
        sess.run(init)
        for epoch in tqdm(range(epochs), unit='epoch'):
            perm = np.random.permutation(x_train.shape[0])
            # iterate over batches
            for bX, bY in batcher(x_train[perm], y_train[perm], batch_size):
                _, t = sess.run([train_op, loss], feed_dict={X: bX.reshape(-1, onehot_dim), Y: bY.reshape(-1, 1)})
                print(t)
        errors = []
        for bX, bY in batcher(x_test, y_test):
            errors.append(sess.run(error, feed_dict={X: bX.reshape(-1, onehot_dim), Y: bY.reshape(-1, 1)}))
            print(errors)
        RMSE = np.sqrt(np.array(errors).mean())
        print(RMSE)

3:xlearn流程以及抽取embeding向量

3.1:数据处理

将数据转换为xlearn所需要的格式,对于 LR 和 FM 算法而言,我们的输入数据格式必须是 CSV 或者 libsvm. 对于 FFM 算法而言,我们的输入数据必须是 libffm 格式

libsvm format:

   y index_1:value_1 index_2:value_2 ... index_n:value_n

   0   0:0.1   1:0.5   3:0.2   ...
   0   0:0.2   2:0.3   4:0.1   ...
   1   0:0.2   2:0.3   5:0.1   ...

CSV format:

   y value_1 value_2 .. value_n

   0      0.1     0.2     0.2   ...
   1      0.2     0.3     0.1   ...
   0      0.1     0.2     0.4   ...

libffm format:

   y field_1:index_1:value_1 field_2:index_2:value_2   ...

   0   0:0:0.1   1:1:0.5   2:3:0.2   ...
   0   0:0:0.2   1:2:0.3   2:4:0.1   ...
   1   0:0:0.2   1:2:0.3   2:4:0.1   ...

3.2:训练模型

# Training task
fm_model = xl.create_fm()              
fm_model.setTrain("./small_train.txt")    # Set the path of training data

# parameter:
#  0. task: binary classification
#  1. learning rate : 0.2
#  2. regular lambda : 0.002
param = {'task':'binary', 'lr':0.2, 'lambda':0.002}

# Train model
fm_model.setTXTModel("./model.txt")
fm_model.fit(param, "./model.out")

模型输出二进制文件,还可以通过 setTXTModel() API 将模型输出成人类可读的 TXT 格式,例如:

对于线性模型来说,TXT 格式的模型输出将每一个模型参数存储在一行。对于 FM 和 FFM,模型将每一个 latent vector 存储在一行。

FM:

bias: 0
i_0: 0
i_1: 0
i_2: 0
i_3: 0
v_0: 5.61937e-06 0.0212581 0.150338 0.222903
v_1: 0.241989 0.0474224 0.128744 0.0995021
v_2: 0.0657265 0.185878 0.0223869 0.140097
v_3: 0.145557 0.202392 0.14798 0.127928

3.3抽取向量

根据编码的index对3.2中的向量进行抽取,一般来说,离散字段每个value都会对应一个向量,每个连续字段会对应一个向量,所以在很多情况下,我们会连续值进行分桶,学习到的信息量会更多。

tips:

在xlearn官方文档中,说到支持在线学习,即加载之前的模型对新加进来数据进行增量训练,然而有一个重大缺陷就是我们无法增加新的特征,需要使用之前的编码对新数据进行编码,每条样本因为onehot之后对权重矩阵的命中率很低,因为大部分数值都是0,所以加载之前的模型进行增量训练其实是有一定道理的,能够加快收敛速度。

参考:

https://blog.csdn.net/xxiaobaib/article/details/92801244

https://zhuanlan.zhihu.com/p/37963267

https://xlearn-doc-cn.readthedocs.io/en/latest/python_api/index.html#id1

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值