AFM 网络介绍与源码浅析

AFM 网络介绍与源码浅析

前言

分享一篇上上个星期看过的论文, 记录下重点吧~

广而告之

可以在微信中搜索 “珍妮的算法之路” 或者 “world4458” 关注我的微信公众号;另外可以看看知乎专栏 PoorMemory-机器学习, 以后文章也会发在知乎专栏中;

Attentional Factorization Machines (AFM)

文章信息

请注意

后面在博文中介绍的代码主要以 https://github.com/princewen/tensorflow_practice/blob/master/recommendation/Basic-AFM-Demo/AFM.py 中的为主; 原作者的代码结构也是类似的, 所以没有关系.

核心观点

文章首先介绍了 FM 的不足, 即对不同的特征交叉 ⟨ v i , v j ⟩ x i x j \langle v_i, v_j\rangle x_ix_j vi,vjxixj 使用的是同一个权重 1, 针对这个不足, 提出了自己的解决方案, 即不同的特征组合对输出的贡献程度是不同的; 具体的实现是引入了 Pair-wise Interaction LayerAttention-based Pooling Layer;
前者输入的 m m m 个向量, 产生 m × ( m − 1 ) 2 \frac{m\times (m - 1)}{2} 2m×(m1) 个交叉的向量, 和 FM 不同的是, FM 得到的是 v i v_i vi v j v_j vj 的内积,但是 Pair-wise Interaction Layer 得到的是 v i v_i vi v j v_j vj 的 element-wise product (哈达玛积), 因此该层的输出是一组向量;
之后这组向量再输入到 Attention-based Pooling Layer 中, 以学习不同特征组合的自适应权重 α \alpha α. 为了防止样本稀疏导致某些 α \alpha α 无法被训练到以及考虑到泛化性, Attention 层还使用了一个网络层对来自 Pair-wise Interaction Layer 的向量进行了处理, 再根据 Softmax 获得每个交叉特征的权重 α \alpha α.

核心观点介绍

Pair-wise Interaction Layer

输入特征 x i x_i xi 经过 Embedding Layer 后, 得到每个特征对应的 embedding, 结果表示为:

E = { v i x i } i ∈ X \mathcal{E}=\left\{\mathbf{v}_{i} x_{i}\right\}_{i \in \mathcal{X}} E={vixi}iX

其中 v i ∈ R k \mathbf{v}_{i} \in \mathbb{R}^{k} viRk, 即特征的大小为 k k k. Pair-wise Interaction Layer 的作用是将这 m m m 个输入的特征进行两两交叉, 从而共生成 m × ( m − 1 ) 2 \frac{m\times (m - 1)}{2} 2m×(m1) 个输出结果; 和 FM 不同的是, 该层对特征交叉的方式不是进行特征间的内积, 而是对两个特征进行 element-wise product, 也就是求哈达马积:

f P I ( E ) = { ( v i ⊙ v j ) x i x j } ( i , j ) ∈ R x f_{P I}(\mathcal{E})=\left\{\left(\mathbf{v}_{i} \odot \mathbf{v}_{j}\right) x_{i} x_{j}\right\}_{(i, j) \in \mathcal{R}_{x}} fPI(E)={(vivj)xixj}(i,j)Rx

其中 R x = { ( i , j ) } i ∈ X , j ∈ X , j > i \mathcal{R}_{x}=\{(i, j)\}_{i \in \mathcal{X}, j \in \mathcal{X}, j>i} Rx={(i,j)}iX,jX,j>i.

代码实现也很简单:

## embeddings 是特征对应的 emb, 大小为 B * F * K
## F 为 field 的大小, 即 field_size
## K 为 embedding 的大小
element_wise_product_list = []
   for i in range(field_size):
       for j in range(i+1, field_size):
           element_wise_product_list.append(tf.multiply(embeddings[:,i,:], embeddings[:,j,:])) # B * K

element_wise_product = tf.stack(element_wise_product_list) # (F * F - 1 / 2) * B * K
element_wise_product = tf.transpose(self.element_wise_product,perm=[1,0,2],name='element_wise_product') # B * (F * F - 1 / 2) *  K

(另外产生 m × ( m − 1 ) 2 \frac{m\times (m - 1)}{2} 2m×(m1) 个交叉特征还看到一种写法, 用了 tf.gather, 在 PNN 的实现中可以看到; 关于 PNN 可以去参看我的博客: Product-based Neural Network (PNN) 介绍与源码浅析; 不行了, 笑哭 , 强行打了一个广告 🤣🤣🤣)

Attention-based Pooling Layer

在这里插入图片描述

该层对 Pair-wise Interaction Layer 产生的 m × ( m − 1 ) 2 \frac{m\times (m - 1)}{2} 2m×(m1) 个交叉特征进行 Attention:

f A t t ( f P I ( E ) ) = ∑ ( i , j ) ∈ R x a i j ( v i ⊙ v j ) x i x j f_{A t t}\left(f_{P I}(\mathcal{E})\right)=\sum_{(i, j) \in \mathcal{R}_{x}} a_{i j}\left(\mathbf{v}_{i} \odot \mathbf{v}_{j}\right) x_{i} x_{j} fAtt(fPI(E))=(i,j)Rxaij(vivj)xixj

其中 α i j \alpha_{ij} αij 为 Attention 分数; 但上式的一个问题是, 如果样本集中 x i x_i xi x j x_j xj 没有同时出现, 那么 α i j \alpha_{ij} αij 可能就无法得到更新 (这个问题其实非常类似 FM 要处理的问题, 比如在引入二阶交叉特征时, 直接采用下式:

y ( x ) = w 0 + ∑ i = 1 n w i x i + ∑ i = 1 n ∑ j = i + 1 n w i j x i x j y(x)=w_{0}+\sum_{i=1}^{n} w_{i} x_{i}+\sum_{i=1}^{n} \sum_{j=i+1}^{n} w_{i j} x_{i} x_{j} y(x)=w0+i=1nwixi+i=1nj=i+1nwijxixj

可能会存在某些 w ^ i j \hat{w}_{i j} w^ij 无法得到更新, 所以 FM 采用矩阵分解的思路, 将公式转换成:

y ( x ) = w 0 + ∑ i = 1 n w i x i + ∑ i = 1 n ∑ j = i + 1 n ⟨ v i , v j ⟩ x i x j y(x)=w_{0}+\sum_{i=1}^{n} w_{i} x_{i}+\sum_{i=1}^{n} \sum_{j=i+1}^{n}\left\langle v_{i}, v_{j}\right\rangle x_{i} x_{j} y(x)=w0+i=1nwixi+i=1nj=i+1nvi,vjxixj

从而解决了因样本稀疏而导致的参数无法及时更新的问题, 更为具体的讨论可以查看 FM 算法介绍以及 libFM 源码简析)

因此, 作者最后考虑的是, 引入 MLP 来对 α \alpha α 进行学习, 公式如下:

a i j ′ = h T ReLU ⁡ ( W ( v i ⊙ v j ) x i x j + b ) a i j = exp ⁡ ( a i j ′ ) ∑ ( i , j ) ∈ R x exp ⁡ ( a i j ′ ) \begin{aligned} a_{i j}^{\prime} &=\mathbf{h}^{T} \operatorname{ReLU} \left(\mathbf{W}\left(\mathbf{v}_{i} \odot \mathbf{v}_{j}\right) x_{i} x_{j}+\mathbf{b}\right) \\ a_{i j} &=\frac{\exp \left(a_{i j}^{\prime}\right)}{\sum_{(i, j) \in \mathcal{R}_{x}} \exp \left(a_{i j}^{\prime}\right)} \end{aligned} aijaij=hTReLU(W(vivj)xixj+b)=(i,j)Rxexp(aij)exp(aij)

其中 W ∈ R t × k , b ∈ R t , h ∈ R t \mathbf{W} \in \mathbb{R}^{t \times k}, \mathbf{b} \in \mathbb{R}^{t}, \mathbf{h} \in \mathbb{R}^{t} WRt×k,bRt,hRt 为 Attention 网络的参数, t t t 表示 Attention 网络隐藏层的大小;

最终的输出表示如下, 主要 Attention 得到交叉特征间的加权求和的结果后, 再与 p \bm{p} p 进行内积, 其中 p ∈ R k \bm{p}\in\mathbb{R}^{k} pRk, 和特征的大小一样.

y ^ A F M ( x ) = w 0 + ∑ i = 1 n w i x i + p T ∑ i = 1 n ∑ j = i + 1 n a i j ( v i ⊙ v j ) x i x j \hat{y}_{A F M}(\mathbf{x})=w_{0}+\sum_{i=1}^{n} w_{i} x_{i}+\mathbf{p}^{T} \sum_{i=1}^{n} \sum_{j=i+1}^{n} a_{i j}\left(\mathbf{v}_{i} \odot \mathbf{v}_{j}\right) x_{i} x_{j} y^AFM(x)=w0+i=1nwixi+pTi=1nj=i+1naij(vivj)xixj

下面看看 Attention-based Pooling Layer 的实现代码:

## field_size 为 Field 的个数, 记为 F
## num_interactions 为交叉特征的个数 (F - 1) * F / 2
## 为了方便, 我后面简记为 P, 表示 pair 的个数; 即 P = (F - 1) * F / 2
num_interactions = int(field_size * (field_size - 1) / 2)
##  wx+b -> relu(wx+b) -> h*relu(wx+b)
## element_wise_product 为 Pair-wise Interaction Layer 的输出结果
## 大小为 B * P * K, 其中 K 为 embedding 的大小 
## weights['attention_w'] 为 Attention 网络的权重, 大小为 (K, A),
## 其中 A 表示 attention size, 使用论文中的数学符号, 表示的是 t
## weights['attention_b'] 为 Attention 网络的偏置, 大小为 (A,)
## 那么 attention_wx_plus_b 的结果为 wx + b
attention_wx_plus_b = tf.reshape(
						tf.add(tf.matmul(
								tf.reshape(element_wise_product, # B * P * K
									shape=(-1, embedding_size) # (B * P) * K
									), 
                                weights['attention_w'] # (K, A)
                                ),
                                weights['attention_b'] # (A,)
                              ),
                        shape=[-1,num_interactions, attention_size]
                        ) # N * P * A

## weights['attention_h'] 表示公式中的 h, 大小为 (A,)
## reduce_sum 相当于做 h 和 relu(Wx + b) 的内积
attention_exp = tf.exp(tf.reduce_sum(
						tf.multiply(
							tf.nn.relu(attention_wx_plus_b),
                            weights['attention_h']),
                        axis=2, keep_dims=True)
                      ) # N * P * 1
## 求 sum(alpha)
attention_exp_sum = tf.reduce_sum(attention_exp,
                                  axis=1, keep_dims=True) # N * 1 * 1
## 相当于求 softmax(alpha)
attention_out = tf.div(attention_exp, attention_exp_sum, 
                       name='attention_out')  # N * P * 1

## 完成对 P 个特征的加权求和
attention_x_product = tf.reduce_sum(
						tf.multiply(attention_out, # N * P * 1
									element_wise_product # N * P * K
									),
						axis=1, name='afm') # N * K
## 输出之前还要和 p 向量进行内积
## weights['attention_p'] 大小为 (K,)
## 注意这里使用的是 matmul, 表示矩阵的乘法
attention_part_sum = tf.matmul(attention_x_product, # N * K
						       weights['attention_p']  # K
						       ) # N * 1

总结

OK, 下午 5 点前写完了, 出门吃烧烤! 😁😁😁 罪恶的周末~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值