龙空技术网

特征交叉系列:完全理解FM因子分解机原理,配代码实战

极客ai 166

前言:

现时我们对“fm 算法 求导”大体比较讲究,咱们都需要剖析一些“fm 算法 求导”的相关知识。那么小编也在网络上收集了一些关于“fm 算法 求导””的相关资讯,希望小伙伴们能喜欢,大家快快来了解一下吧!

关键词:特征交叉推荐算法FM

内容摘要特征交叉和FM背景介绍二阶交互作用的稀疏性FM对学习参数的优化FM的优缺点总结FM在PyTorch下的实践特征交叉和FM背景介绍

推荐算法中将用户特征商品特征,以及上下文的特征输入给一个模型函数进行打分预测用户的交互意愿,而用户兴趣的高低往往不是单一一个独立特征就可以挖掘的,通常多个特征的组合形成的一个特征组更容易发现用户兴趣的一般规律,比如对<user年龄,user性别>做特征组合让模型学习历史行为日志中这个局部人群的兴趣爱好,再如<user性别, item类目>做特征组合让模型学习是否某一类的商品在用户性别上有明显的兴趣区分度,这种将一对或者多维特征进行组合形成新的特征输入模型的方式叫做特征交叉,特征交叉自然可以增强模型的非线性能力,提高模型的拟合能力。

特征交叉可以从特征入手,比如模型输入时就添加用户和商品的交互特征,类似A用户过去一段滑动窗口下对B商品所属类目的点击次,也可以从模型入手,即设计一个网络结构能够对原始输入特征进行自动交叉和组合,让模型能够学习到这种特征交之间互关联的模式,在传统机器学习领域能够自动特征交叉的算法包括GBDT提升树FM因子分解机

FM提出于2010年,旨在解决稀疏数据下的特征交叉问题,相比于线性的逻辑回归,FM引入和二阶交互项。

FM原理提要二阶交互作用的稀疏性

传统的逻辑回归仅仅是给每个特征单独分配了一个权重,FM在此基础上加入特征两两之间的二阶相乘的特征,公式如下

FM公式1

举例A用户是一个女青年,包含性别和年龄段两个特征,做onehot之后一阶特征的表达是[0, 1, 1, 0, 0],所有特征交叉之后形成5×5=25个新特征,其中只取上三角不连对角线还剩10个特征,因此二阶特征之后还需要加入一个类似[0, 0, 0, 0, 1, 0, 0, 0, 0, 0]的所有二阶组合的全集,其中唯一的一个1是性别女和年龄青年对角线命中的组合情况,FM对每一对特征进行交叉,只有两个特征都是1交叉才有意义,否则交叉结果为0,由此可见数据是极度稀疏的,而这种以离散变量为主的数据在推荐算法场景很常见。

特征交叉举例

设onehot之后的特征数量为n,一阶特征模型需要学习参数有n+1个(wx+b),而二阶模型需要学习的参数有n+1+n(n-1)/2个,参数个数随着n的扩大呈平方增长。另一方面由于二阶特征里面绝大多数都是0,在进行神经网络反向链式求导的时候是包含特征值这个因子的,因此计算出的梯度为0二阶特征的权重无法更新,因此暴力求解所有二阶参数不可取。

FM对学习参数的优化

FM的精髓是不直接求解特征交叉权重,而是将一对特征交叉进行拆解,拆解为两个底层的特征属性的求解,即将二阶特征的权重计算转化为该对一阶特征对应的向量的内积,如下图所示

该对一阶特征对应的向量的内积

其中vi代表第i个一阶特征对应的隐向量,它是一个一维向量,长度为k(k << n),这时FM模型方程如下

FM公式2

因此只要计算出每个一阶特征对应的隐向量即可,这些隐向量组成一个n × k的矩阵,模型从求解n+1+n(n-1)/2个二阶参数优化为只要求解n × k参数即可,参数的数量随着特征数量n是线性的,优化前是平方级别。

FM的计算公式可以进一步计算如下

FM公式3

上图是最终梯度的公式,其中红色框内的f代表隐向量的某个k位置上的分量,注意红色框内是和xi中的i无关的,也就是说xi和xi+1是共用这个红色框的,因此所有特征x1->n的梯度计算可以只求一次红色框内部分即可,单个i单个f需要计算n次,则单个i所有f的复杂度是O(kn),由于红色框内共享,因此计算所有i的复杂度也是O(kn),因此FM可以在特征数量的线性时间内完成训练

FM的优缺点总结

FM的优势

照顾到所有特征和二阶交互项,而不是像GBDT只对部分特征做选择交叉,FM能够很好的挖掘所有特征的二阶交叉组合适合高维稀疏特征,通过引入隐向量解决二阶参数难以训练的问题FM不论训练还是预测,复杂度随着特征增长都是线性的

FM的缺点

虽然考虑了特征的交叉,但是表达能力仍然有限,只能对二阶进行交叉,不能高阶交叉FM会对所有特征做两两交叉,从业务上某些交叉是没有必要的,存在冗余由第2点进一步衍生,因为过多的两两交叉容易导致特征之间互相拉扯,形成矛盾,特征的隐向量不能做到对特征交叉表达的全局适配,因此引出下一篇特征交叉系列:Field-aware FM 场感知因子分解机原理和实践中的FFM算法,FFM引入场的概念,为每一个特征准备多套隐向量,通过场来隔离不同对特征的交叉,解决单一隐向量导致的特征拉扯问题FM在Pytorch下的实践

采用用户的购买记录流水作为训练数据,用户侧特征是年龄,性别,会员年限等离散特征,商品侧特征采用商品的二级类目,产地,品牌三个离散特征,随机构造负样本,使用LR和FM分别进行训练,期望模型能够学习到用户特征和商品特征的匹配程度

没有特征交叉的LR训练

采用Pytorch构建一个Linear层即可

class LR(nn.Module):    def __init__(self, feature_size, k_dim=4):        super(FM, self).__init__()        self.linear = nn.Linear(feature_size, 1)    def forward(self, x):        linear_part = self.linear(x)  # [None, 1]        output = t.sigmoid(linear_part)        return output.squeeze(dim=1)

训练日志如下,多次测试验证集AUC在0.608左右

epoch: 10 step: 598 loss: 0.680474042892456 auc: 0.5920163571046396epoch: 10 step: 600 loss: 0.665024995803833 auc: 0.6218916768547087[evaluation] loss: 0.6739967465400696 auc: 0.6048385095987285本轮auc比之前最大auc下降:0.00019671459790582269, 当前最大auc: 0.6050352241966344---------early stop!----------
带有特征交叉的FM训练

在原有LR的网络基础上增加FM中的特征交叉层Cross,最终模型输出层是线性Linear和Cross相加的结果,网络层代码如下

class Cross(nn.Module):    def __init__(self, feature_size, k_dim=4):        super(Cross, self).__init__()        self.v = nn.Parameter(t.randn(feature_size, k_dim))        nn.init.xavier_normal_(self.v)    def forward(self, x):        inter_part1 = t.mm(x, self.v)  # [None, k]        inter_part2 = t.mm(t.pow(x, 2), t.pow(self.v, 2))  # [None, k]        inter = 0.5 * t.sum(t.sub(t.pow(inter_part1, 2), inter_part2), 1, keepdim=True)  # [None, 1]        return interclass FM(nn.Module):    def __init__(self, feature_size, k_dim=4):        super(FM, self).__init__()        self.linear = nn.Linear(feature_size, 1)        self.cross = Cross(feature_size, k_dim)    def forward(self, x):        linear_part = self.linear(x)  # [None, 1]        inter = self.cross(x)  # [None, 1]        output = linear_part + inter  # [None, 1]        output = t.sigmoid(output)        return output.squeeze(dim=1)

观察模型需要训练的参数,一共1 + n + kn个参数

    for i, j in model.named_parameters():        print(i, j.numel())>>> linear.weight 72>>> linear.bias 1>>> cross.v 288

部分训练代码如下

    model = FM(feature_size=72, k_dim=4)    criterion = nn.BCELoss(reduction="mean")    optimizer = t.optim.Adam(model.parameters(), lr=0.002, weight_decay=0)    step = 0    val_auc_list = []    early_stop_flag = False    for epoch in range(10):        for index, (batch_x, batch_y) in enumerate(train_loader):            model.train()            batch_x, batch_y = batch_x.to(device).float(), batch_y.to(device).float()            optimizer.zero_grad()            output = model(batch_x)            loss = criterion(output, batch_y)            loss.backward()            optimizer.step()            step += 1            if step % 2 == 0:                auc = roc_auc_score(batch_y.detach().numpy(), output.detach().numpy())                print("epoch: {} step: {} loss: {} auc: {}".format(epoch + 1, step, loss.item(), auc))            if step % 10 == 0:                pred, target = [], []                model.eval()                with t.no_grad():                    for _, (batch_x_test, batch_y_test) in enumerate(test_loader):                        batch_x_test, batch_y_test = batch_x_test.to(device).float(), batch_y_test.to(device).float()                        output = model(batch_x_test)                        loss = criterion(output, batch_y_test)                        pred += list(output.numpy())                        target += list(batch_y_test.numpy())                    auc = roc_auc_score(target, pred)                    print("[evaluation] loss: {} auc: {}".format(loss.item(), auc))                    diff_auc = (auc - max(val_auc_list)) if len(val_auc_list) else 0                    val_auc_list.append(auc)                    print("本轮auc比之前最大auc{}:{}, 当前最大auc: {}\n".format("上升" if diff_auc > 0 else "下降", abs(diff_auc),                                                                     max(val_auc_list)))                    if early_stop_auc(val_auc_list, windows=5):                        print("{:-^30}".format("early stop!"))                        early_stop_flag = True                        break        if early_stop_flag:            break

训练日志如下,多次训练模型在验证集的AUC在0.627

epoch: 11 step: 676 loss: 0.6699222922325134 auc: 0.6260850470680339epoch: 11 step: 678 loss: 0.664801836013794 auc: 0.6229823551975451epoch: 11 step: 680 loss: 0.673725962638855 auc: 0.6091113028472821[evaluation] loss: 0.6770298480987549 auc: 0.6267347541949988本轮auc比之前最大auc下降:0.00022016626855891896, 当前最大auc: 0.6269549204635577---------early stop!----------

FM的验证集AUC比LR上升了2个百分点左右,说明了FM捕捉交叉特征的有效性。

标签: #fm 算法 求导