推荐算法-AFM

推荐算法-AFM

  推荐算法-AFM,这篇文章也是在FM的基础上做工作。这篇文章是针对特征之间组合时,不同的特征都是用同样的向量去做。即每一个特征和其它的特征进行组合时,都是采用同一个向量,缺乏不同特征之间的关联性不同,应该采用不同的向量。解决这个问题的一个思路就是FFM,即每一个特征针对每一个的field生成一个特征向量,即在进行特征组合时,采用不同的向量表示去做。本文是解决这个问题的另一种思路,也就是对不同的特征组合赋予不同的权值,而且这个权值是可学习的,体现了模型对不同的特征组合的关注度不同。我的理解是对与最后的分类贡献程度较高的特征组合,会赋予较高的权值。
  说到Attention,在cv、nlp、推荐这三个领域都有使用,可见Attention的作用之大。有人在cv领域使用Attention,可以找出深度模型作出最后判决所依据的区域,在nlp领域通过Attention可以找出关键词。在推荐中,可以为不同的特征组合赋予权值。总体的思路就是模型对不同的特征组合关注度是不同的。

AFM网络结构

在这里插入图片描述
从上图中可以看出,前半部分AFM的模型结构和NFM的基本一致,都是先把特征映射到固定的长度,然后计算FM部分,即上面的Pair-wise Interaction Layer部分。但是后面的就不一样了,NFM是直接在FM后面接入了一个NN层,而AFM是生成了一个Attention-based Pooling,即生成一组权重向量,然后再和Pair-wise部分对应相乘。AFM没有继续对这部分特征进行深层神经网络的学习,或许引入NN会进一步提升模型的精度。
  FM的公式大家应该还记得,这里先回归一下:
在这里插入图片描述
在这里插入图片描述
attention就是对上面 V i , V j x i x j V_{i},V_{j}x_{i}x_{j} Vi,Vjxixj部分进行加权。最终AFM的公式表达为:
y A F M = w 0 + ∑ i = 1 n w i x i + P T ∑ i = 1 ∑ j = i + 1 a i j &lt; V i , V j &gt; x i x j y_{AFM}=w_{0}+\sum_{i=1}^{n}w_{i}x_{i}+P^{T}\sum_{i=1}\sum_{j=i+1}a_{ij}&lt;V_{i},V_{j}&gt;x_{i}x_{j} yAFM=w0+i=1nwixi+PTi=1j=i+1aij<Vi,Vj>xixj
相比于原始的FM公式,AFM部分多了一组权重系数 a i j a_{ij} aij和向量 P T P^{T} PT。我们只需要知道这两个数据的表达式,就可以实现AFM了。
a i j ′ = h T R e L U ( W &lt; V i , V j &gt; x i x j + b ) a_{ij}^{&#x27;}=h^{T}ReLU(W&lt;V_{i},V_{j}&gt;x_{i}x_{j}+b) aij=hTReLU(W<Vi,Vj>xixj+b) ,
a i j = e x p ( a i j ′ ) ∑ i , j e x p ( a i j ′ ) a_{ij}=\frac{exp(a_{ij}^{&#x27;})}{\sum_{i,j}exp(a_{ij}^{&#x27;})} aij=i,jexp(aij)exp(aij)
上面这两个公式表达了 a i j a_{ij} aij的计算方式,第一个公式中又引入了参数 w 、 h w、h wh,因为两个特征相乘之后得到的特征长度不变,仍为embeddingSIze,w是要根据这部分特征计算attention部分的权重,因此w的大小为 K ∗ A K*A KA K为embedding部分的长度,A为attention部分的长度, h h h部分的长度也为 A A A。第二个公式其实就是softmax表达式。其实最后就是对 p a i r s = f i e l d S I z e ∗ ( f i e l d S I z e − 1 ) / 2 pairs=fieldSIze*(fieldSIze-1)/2 pairs=fieldSIze(fieldSIze1)/2个特征组合分别赋予权重,即上面 a i j a_{ij} aij的长度为pairs。

AFM模型构建

  模型构建部分就比较简单了,其实就是先把embedding部分的权重构建出来,然后再把Attention的权重构建出来就可以了。
&emsp 权重构建,embedding部分就是[featureSIze, embeddingSize],以及一次项部分[featureSize,1]。接下来就是Attention部分的权重了,Attention部分的参数有 W 、 h 、 P 、 b W、h、P、b WhPb,他们的shape分别为:
W : [ e m b e d d i n g S i z e , a t t e n t i o n S i z e ] W: [embeddingSize, attentionSize] W:[embeddingSize,attentionSize]
b : [ a t t e n t i o n S i z e , ] b: [attentionSize, ] b:[attentionSize,]
h : [ a t t e n t i o n S i z e , ] h: [attentionSize, ] h:[attentionSize,]
p : [ a t t e n t i o n S i z e , 1 ] p: [attentionSize, 1] p:[attentionSize,1]
然后我们可以构建权重了:

    def _initWeights(self):
        weights = dict()
        # embedding
        weights['feature_embedding'] = tf.Variable(tf.random_normal(shape=[self.featureSize, self.embeddingSize],
                                                                    mean=0.0, stddev=1.0), name='feature_embedding')
        weights['feature_bias'] = tf.Variable(tf.random_normal(shape=[self.featureSize, 1], mean=0.0, stddev=1.0),
                                              name='feature_bias')
        weights['bias'] = tf.Variable(tf.constant(0.1), name='bias')

        # attention
        # w: K * A
        # b: A
        # h: A
        # p: K * 1
        weights['attention_w'] = tf.Variable(tf.random_normal(shape=[self.embeddingSize, self.attentionSize], mean=0.0,
                                                              stddev=1.0), name='attention_w')
        weights['attention_b'] = tf.Variable(tf.random_normal(shape=[self.attentionSize, ], mean=0.0, stddev=1.0),
                                             name='attention_b')
        weights['attention_h'] = tf.Variable(tf.random_normal(shape=[self.attentionSize, ], mean=0.0, stddev=1.0),
                                             name='attention_h')
        weights['attention_p'] = tf.Variable(tf.random_normal(shape=[self.embeddingSize, 1]))

        return weights

  计算图的构建,首先是计算embedding部分,然后是计算attention部分的权重。
一些输入的设置:

        self.weights = self._initWeights()

        self.featureIndex = tf.placeholder(shape=[None, None], dtype=tf.int32, name='featureIndex')
        self.featureValue = tf.placeholder(shape=[None, None], dtype=tf.float32, name='featureValue')
        self.label = tf.placeholder(shape=[None, 1], dtype=tf.float32, name='label')

        # self.dropoutKeep = tf.placeholder(shape=[None, ], dtype=tf.float32, name='dropoutKeep')
        self.trainPhrase = tf.placeholder(dtype=tf.bool, name='trainPhrase')

embedding部分:

        # embedding
        self.embedding = tf.nn.embedding_lookup(self.weights['feature_embedding'], self.featureIndex)  # N * F * K
        featureValue = tf.reshape(self.featureValue, shape=[-1, self.fieldSize, 1])
        self.embedding = tf.multiply(self.embedding, featureValue)  # N*F*K

接下来我们就要计算特征之间的组合了,两两组合,这次是采用的for循环了,在PNN中我们是先找出来每一个特征组合pair的索引,然后利用tf.gather来把这些向量找出来,最后实现相乘。特征向量组合的的代码:
&lt; V i , V j &gt; x i x j &lt;V_{i},V_{j}&gt;x_{i}x_{j} <Vi,Vj>xixj &lt; V i , V j &gt; &lt;V_{i},V_{j}&gt; <Vi,Vj>得到的是一个长度为embeddingSize的向量

        elementWiseProduct = []
        for i in range(0, self.fieldSize-1):
            for j in range(i+1, self.fieldSize):
                elementWiseProduct.append(tf.multiply(self.embedding[:, i, :], self.embedding[:, j, :]))
        self.elementWiseProduct = tf.stack(elementWiseProduct)  # f*(f-1)/2 * N * k
        self.elementWiseProduct = tf.transpose(self.elementWiseProduct, [1, 0, 2], )  # N * (f*(f-1)/2) * K

此时self.elementWiseProduct的shape为  N ∗ f ∗ ( f − 1 ) / 2 ∗ K N * f*(f-1)/2 * K Nf(f1)/2K f为fieldSize,k为embeddingSize。
然后就是计算 W &lt; V i , V j &gt; x i x j + b W&lt;V_{i},V_{j}&gt;x_{i}x_{j}+b W<Vi,Vj>xixj+b,W的shape为 K ∗ A K*A KA,因此首先要把self.elementWiseProduct的shape调整一下。

        self.numPairs = int(self.fieldSize * (self.fieldSize-1)/2)
        self.wxPlusB = tf.matmul(tf.reshape(self.elementWiseProduct, shape=[-1, self.embeddingSize]),  # (N*pairs) * K
                                 self.weights['attention_w']) + self.weights['attention_b']  # (N*pairs) * A
        self.wxPlusB = tf.reshape(self.wxPlusB, shape=[-1, self.numPairs, self.attentionSize])  # N*pairs*A

因为 W W W &lt; V i i , V j &gt; &lt;Vi_{i},V_{j}&gt; <Vii,Vj>相乘是矩阵相乘,所以要遵循矩阵相乘时尺寸的要求。计算完之后再对其进行shape的调整,调整为 N ∗ p a i r s ∗ A N*pairs*A NpairsA
然后就是计算 a i j ′ = h T R e L U ( W &lt; V i , V j &gt; x i x j + b ) a_{ij}^{&#x27;}=h^{T}ReLU(W&lt;V_{i},V_{j}&gt;x_{i}x_{j}+b) aij=hTReLU(W<Vi,Vj>xixj+b)

        self.attentionExp = tf.exp(tf.reduce_sum(tf.multiply(tf.nn.relu(self.wxPlusB), self.weights['attention_h']),
                                                 axis=2, keep_dims=True))  # N*pairs*1
        self.attentionExpSum = tf.reduce_sum(self.attentionExp, axis=1, keep_dims=True)  # N*1*1

第一行代码是计算 a i j ′ a_{ij}^{&#x27;} aij第二行代码是对所有的 a i j ′ a_{ij}^{&#x27;} aij求和,即为后面的softmax计算 a i j a_{ij} aij做准备。上面这两行的keep_dims还是要加的,如果不加的话,在某一维度上进行求和,求和之后得到的结果就会少一个维度。
计算 a i j a_{ij} aij

        self.attentionOut = tf.div(self.attentionExp, self.attentionExpSum)  # N*pairs*1

接下来就是对每一对特征组合进行权重分配了,

        self.attention_x_product = tf.reduce_sum(tf.multiply(self.attentionOut, self.elementWiseProduct), axis=1)  # N*K
        self.attentionPartSum = tf.matmul(self.attention_x_product, self.weights['attention_p'])  # N*1

最后把一次项以及偏置项计算出来加到一起就可以了,

        # first order
       self.yFirstOrder = tf.nn.embedding_lookup(self.weights['feature_bias'], self.featureIndex)  # N*F
       self.yFirstOrder = tf.multiply(self.yFirstOrder, featureValue)  # N*F*1
       self.yFirstOrder = tf.reduce_sum(self.yFirstOrder, axis=2)  # N*F
       # bias
       self.yBias = self.weights['bias'] * tf.ones_like(self.label)

       self.out = tf.add_n([tf.reduce_sum(self.yFirstOrder, axis=1, keep_dims=True),
                            self.attentionPartSum, self.yBias], name='out_afm')

然后就是模型的loss的设置、以及初始化等问题了。因为该模型没有全连接层,所以模型部分就是这样。

  这篇文章就是对FM的二次项部分进行了加权操作,没有太大的改进,上一篇NFM是对FM的输出向量进行了几层全连接操作,思路都比较简单。不知道这样的模型在实际生产环境中是否有使用。

参考

https://www.comp.nus.edu.sg/~xiangnan/papers/ijcai17-afm.pdf
https://www.jianshu.com/p/83d3b2a1e55d

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
AFM(Attentional Factorization Machines)是一种结合了因子分解机(Factorization Machines)和注意力机制(Attention Mechanism)的推荐算法。它的目的是在因子分解机的基础上引入注意力机制,从而提高推荐系统的效果。下面是利用 PyTorch 实现 AFM 的基本步骤: 首先,需要导入所需的库和数据集。例如: ``` import torch import torch.nn as nn from torch.utils.data import DataLoader, Dataset from sklearn.model_selection import train_test_split import pandas as pd # 读取数据集 data = pd.read_csv('data.csv') ``` 然后,需要对数据进行预处理,包括将类别特征进行 one-hot 编码、将数值特征进行归一化等。例如: ``` # 对类别特征进行 one-hot 编码 cat_cols = ['user_id', 'item_id', 'genre'] for col in cat_cols: data[col] = data[col].astype('category') data = pd.get_dummies(data, columns=cat_cols) # 对数值特征进行归一化 num_cols = ['age', 'rating'] for col in num_cols: min_val = data[col].min() max_val = data[col].max() data[col] = (data[col] - min_val) / (max_val - min_val) ``` 接下来,需要将数据集划分为训练集和测试集,并将其转换为 PyTorch 的 Dataset 和 DataLoader。例如: ``` # 划分训练集和测试集 train_data, test_data = train_test_split(data, test_size=0.2) # 将数据集转换为 PyTorch 的 Dataset class AFMDataset(Dataset): def __init__(self, data): self.X = torch.tensor(data.drop('rating', axis=1).values, dtype=torch.float32) self.y = torch.tensor(data['rating'].values, dtype=torch.float32) def __len__(self): return len(self.X) def __getitem__(self, idx): return self.X[idx], self.y[idx] train_dataset = AFMDataset(train_data) test_dataset = AFMDataset(test_data) # 将数据集转换为 PyTorch 的 DataLoader train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False) ``` 然后,需要定义 AFM 模型。AFM 模型由两个部分组成:FM 层和注意力层。FM 层用于学习特征之间的交互关系,注意力层用于计算每个特征的权重。例如: ``` class FM(nn.Module): def __init__(self, num_features): super().__init__() self.num_features = num_features self.w = nn.Parameter(torch.randn(num_features)) self.bias = nn.Parameter(torch.randn(1)) def forward(self, x): interactions = torch.sum(torch.mm(x, x.t()) * self.w, dim=1) return interactions + self.bias class Attention(nn.Module): def __init__(self, num_features): super().__init__() self.num_features = num_features self.W = nn.Parameter(torch.randn(num_features, num_features)) self.bias = nn.Parameter(torch.randn(1)) def forward(self, x): scores = torch.mm(x, self.W) scores = torch.mm(scores, x.t()) scores = nn.functional.softmax(scores, dim=1) outputs = torch.mm(scores, x) return outputs + self.bias class AFM(nn.Module): def __init__(self, num_features): super().__init__() self.fm = FM(num_features) self.attention = Attention(num_features) def forward(self, x): fm_output = self.fm(x) attention_output = self.attention(x) output = fm_output + attention_output return torch.sigmoid(output) ``` 最后,需要定义损失函数和优化器,并训练模型。例如: ``` # 定义损失函数和优化器 criterion = nn.BCELoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 训练模型 model = AFM(num_features=train_data.shape[1]-1) for epoch in range(10): for i, (inputs, targets) in enumerate(train_loader): optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step() # 计算测试集上的准确率 correct = 0 total = 0 with torch.no_grad(): for inputs, targets in test_loader: outputs = model(inputs) predicted = torch.round(outputs) total += targets.size(0) correct += (predicted == targets).sum().item() accuracy = correct / total print(f'Epoch {epoch+1}, Test Accuracy: {accuracy:.2f}') ``` 通过以上步骤,就可以利用 PyTorch 实现 AFM 推荐算法了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值