NFM算法

目录

一:背景

二:原理

2.1 Embedding Layer

2.2 Bi-Interaction Layer

三:代码实现

四:总结

NFM主要的特点:


一:背景

在CTR预估中,为了解决稀疏特征的问题,学者们提出了FM模型来建模特征之间的交互关系。但是FM模型只能表达特征之间两两组合之间的关系,无法建模两个特征之间深层次的关系或者说多个特征之间的交互关系,因此学者们通过Deep Network来建模更高阶的特征之间的关系。因此 FM和深度网络DNN的结合也就成为了CTR预估问题中主流的方法。有关FM和DNN的结合有两种主流的方法,并行结构和串行结构。两种结构的理解以及实现如下表所示:

结构描述常见模型
并行结构FM部分和DNN部分分开计算,只在输出层进行一次融合得到结果DeepFM,DCN,Wide&Deep
串行结构将FM的一次项和二次项结果(或其中之一)作为DNN部分的输入,经DNN得到最终结果FNN,NFM,AFM

今天介绍的NFM模型(Neural Factorization Machine),便是串行结构中一种较为简单的网络模型。NFM摒弃了直接把嵌入向量拼接输入到神经网络的做法在嵌入层之后增加了Bi-Interaction操作来对二阶组合特征进行建模。这使得low level的输入表达的信息更加的丰富,极大的提高了后面隐藏层学习高阶非线性组合特征的能力。

二:原理

公式:

其中第一项和第二项是线性回归部分,与FM相似,FM模拟数据的全局偏差和特征权重。第三项f(x)是NFM的核心组成部分,用于建模特征交互。它是一个多层前馈神经网络。如图2所示,接下来,我们一层一层地阐述f(x)的设计。

模型结构:

preview

2.1 Embedding Layer

和其他的DNN模型处理稀疏输入一样,Embedding将输入转换到低维度的稠密的嵌入空间中进行处理。这里做稍微不同的处理是,使用原始的特征值乘以Embedding vector,使得模型也可以处理real valued feature。

2.2 Bi-Interaction Layer

Bi是Bi-linear的缩写,这一层其实是一个pooling层操作,它把很多个向量转换成一个向量,其实它就是计算FM中的二次项的过程,因此得到的向量维度就是我们的Embedding的维度:

代码:

            with tf.variable_scope('interaction_layer'):
                self.sum_square_emb = tf.square(tf.reduce_sum(self.embeddings, axis=1))  # [None, embedding_size]
                self.square_sum_emb = tf.reduce_sum(tf.square(self.embeddings), axis=1)  # [None, embedding_size]
                self.fully_out = 0.5 * tf.subtract(self.sum_square_emb, self.square_sum_emb)  # [None, embedding_size]

Hidden Layers就是我们的DNN部分,将Bi-Interaction Layer得到的结果接入多层的神经网络进行训练,从而捕捉到特征之间复杂的非线性关系。

在进行多层训练之后,将最后一层的输出求和同时加上一次项和偏置项,就得到了我们的预测输出:

三:代码实现

 with tf.name_scope('Embedding_Layer'):
                self.embeddings = tf.nn.embedding_lookup(self.weights['feature_embeddings'],
                                                         self.feature_index)  # [None, field_size, embedding_size]
                feat_value = tf.reshape(self.feature_value, shape=[-1, self.field_size, 1])  # [None, field_size, 1]
                self.embeddings = tf.multiply(self.embeddings, feat_value)  # [None, field_size, embedding_size]

            with tf.name_scope('linear_part'):
                self.linear_part = tf.nn.embedding_lookup(self.weights['linear_w'],
                                                          self.feature_index)  # [None, field_size, 1]
                self.linear_part = tf.reduce_sum(tf.multiply(self.linear_part, feat_value),
                                                 axis=2)  # [None, field_size]
                self.linear_part = tf.nn.dropout(self.linear_part, self.dropout_keep_fm[0])  # [None, field_size]
                self.linear_out = tf.reduce_sum(self.linear_part, axis=1, keepdims=True)  # [None, 1]
                self.w0 = tf.multiply(self.biases['w0'], tf.ones_like(self.linear_out))  # [None, 1]

            with tf.variable_scope('interaction_layer'):
                self.sum_square_emb = tf.square(tf.reduce_sum(self.embeddings, axis=1))  # [None, embedding_size]
                self.square_sum_emb = tf.reduce_sum(tf.square(self.embeddings), axis=1)  # [None, embedding_size]
                self.fully_out = 0.5 * tf.subtract(self.sum_square_emb, self.square_sum_emb)  # [None, embedding_size]

            with tf.name_scope('fully_layer'):
                for i in range(len(self.deep_layers)):
                    self.fully_out = tf.add(tf.matmul(self.fully_out, self.weights[i]), self.biases[i])
                    self.fully_out = self.deep_layers_activation(self.fully_out)
                    if (self.batch_norm):
                        self.fully_out = self.batch_norm_layer(self.fully_out, self.train_phase)
                    self.fully_out = tf.nn.dropout(self.fully_out, keep_prob=self.dropout_fm[i])

            with tf.name_scope('out'):
                self.out = tf.add_n([self.w0, self.linear_out, self.fully_out])  # # yAFM = w0 + wx + f(x)

四:总结

NFM主要的特点:

1.NFM核心就是在NN中引入了Bilinear Interaction(Bi-Interaction) pooling操作。基于此,NN可以在low level就学习到包含更多信息的组合特征。

2. 通过dnn来学习高阶的非线性的组合特征。

3. NFM相比于上面提到的DNN模型,模型结构更浅、更简单(shallower structure),但是性能更好,训练和调整参数更加容易。

所以,依旧是FM+DNN的组合套路,不同之处在于如何处理Embedding向量,这也是各个模型重点关注的地方。现在来看业界就如何用DNN来处理高维稀疏的数据并没有一个统一普适的方法,依旧在摸索中。

参考:

https://www.jianshu.com/p/4e65723ee632

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是基于 PyTorch 实现 NFM 算法的代码,同时使用 AUC 进行评估: 首先,我们需要导入必要的库: ```python import torch import torch.nn as nn import torch.optim as optim from sklearn.metrics import roc_auc_score ``` 接着,我们定义 NFM 模型的代码如下: ```python class NFM(nn.Module): def __init__(self, num_feats, embed_dim, hidden_dim): super(NFM, self).__init__() self.embedding_layer = nn.Embedding(num_feats, embed_dim) self.linear_layer = nn.Linear(embed_dim, hidden_dim) self.interaction_layer = nn.Sequential( nn.Linear(embed_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, 1), nn.Sigmoid() ) def forward(self, x): embeddings = self.embedding_layer(x) square_sum = torch.sum(embeddings, dim=1) ** 2 sum_square = torch.sum(embeddings ** 2, dim=1) linear = self.linear_layer(torch.mean(embeddings, dim=1)) interaction = torch.sum(self.interaction_layer(embeddings), dim=1) output = linear + interaction - square_sum + sum_square return output ``` 在这里,我们将 NFM 模型分为三个部分: 1. `embedding_layer`:嵌入层,将输入的特征进行嵌入,得到每个特征的向量表示。 2. `linear_layer`:线性层,对特征嵌入后得到的向量进行平均,得到一个一维向量,并通过一个全连接层进行线性变换。 3. `interaction_layer`:交互层,对每个特征嵌入后得到的向量进行交互计算,并通过一系列层进行非线性变换,最后得到一个交互权重。 在 `forward` 函数中,我们首先计算了平方和以及和的平方,然后分别对线性部分和交互部分进行计算,最后将线性部分、交互部分、平方和以及和的平方相加得到最终的输出。 接下来,我们定义训练函数和测试函数: ```python def train(model, train_loader, optimizer, criterion): model.train() for batch_idx, (X, y) in enumerate(train_loader): optimizer.zero_grad() output = model(X) loss = criterion(output, y) loss.backward() optimizer.step() def test(model, test_loader): model.eval() y_true = [] y_pred = [] with torch.no_grad(): for batch_idx, (X, y) in enumerate(test_loader): output = model(X) y_true.extend(y.tolist()) y_pred.extend(output.tolist()) auc = roc_auc_score(y_true, y_pred) print('AUC:', auc) ``` 在这里,我们定义了一个训练函数和一个测试函数。训练函数中,我们首先将模型设为训练模式,然后对于每一个 batch 进行一次前向传播、计算 loss、反向传播和更新参数的操作。测试函数中,我们将模型设为评估模式,然后对于测试集中的每一个样本进行一次前向传播,并记录真实标签和预测概率,最后计算 AUC。 最后,我们读取数据、初始化模型、定义损失函数和优化器,并开始训练和测试: ```python # 读取数据 train_data = torch.Tensor([[1, 2, 3, 0], [4, 5, 6, 1], [7, 8, 9, 0], [10, 11, 12, 1]]) test_data = torch.Tensor([[1, 2, 3, 1], [4, 5, 6, 0], [7, 8, 9, 1], [10, 11, 12, 0]]) train_loader = torch.utils.data.DataLoader(train_data, batch_size=2, shuffle=True) test_loader = torch.utils.data.DataLoader(test_data, batch_size=2, shuffle=True) # 初始化模型 model = NFM(num_feats=4, embed_dim=2, hidden_dim=4) # 定义损失函数和优化器 criterion = nn.BCEWithLogitsLoss() optimizer = optim.Adam(model.parameters(), lr=0.01) # 训练和测试 for epoch in range(10): train(model, train_loader, optimizer, criterion) test(model, test_loader) ``` 在这里,我们使用了一个简单的数据集作为示例,共有 4 个样本,每个样本都有 4 个特征,其中前三个特征是类别型特征,最后一个特征是二分类标签。在初始化模型时,我们将类别型特征嵌入到 2 维向量中,然后将线性层和交互层的隐藏维度都设置为 4。在优化器中,我们使用了 Adam 算法作为优化器,学习率为 0.01。在训练过程中,我们进行了 10 轮迭代,每轮迭代后都对模型进行一次测试,并输出 AUC。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值