推薦算法-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就是對上面Vi,VjxixjV_{i},V_{j}x_{i}x_{j}部分進行加權。最終AFM的公式表達爲:
yAFM=w0+i=1nwixi+PTi=1j=i+1aij<Vi,Vj>xixjy_{AFM}=w_{0}+\sum_{i=1}^{n}w_{i}x_{i}+P^{T}\sum_{i=1}\sum_{j=i+1}a_{ij}<V_{i},V_{j}>x_{i}x_{j}
相比於原始的FM公式,AFM部分多了一組權重係數aija_{ij}和向量PTP^{T}。我們只需要知道這兩個數據的表達式,就可以實現AFM了。
aij=hTReLU(W<Vi,Vj>xixj+b)a_{ij}^{'}=h^{T}ReLU(W<V_{i},V_{j}>x_{i}x_{j}+b) ,
aij=exp(aij)i,jexp(aij)a_{ij}=\frac{exp(a_{ij}^{'})}{\sum_{i,j}exp(a_{ij}^{'})}
上面這兩個公式表達了aija_{ij}的計算方式,第一個公式中又引入了參數whw、h,因爲兩個特徵相乘之後得到的特徵長度不變,仍爲embeddingSIze,w是要根據這部分特徵計算attention部分的權重,因此w的大小爲KAK*A K爲embedding部分的長度,A爲attention部分的長度,hh部分的長度也爲AA。第二個公式其實就是softmax表達式。其實最後就是對pairs=fieldSIze(fieldSIze1)/2pairs=fieldSIze*(fieldSIze-1)/2個特徵組合分別賦予權重,即上面aija_{ij}的長度爲pairs。

AFM模型構建

  模型構建部分就比較簡單了,其實就是先把embedding部分的權重構建出來,然後再把Attention的權重構建出來就可以了。
&emsp 權重構建,embedding部分就是[featureSIze, embeddingSize],以及一次項部分[featureSize,1]。接下來就是Attention部分的權重了,Attention部分的參數有WhPbW、h、P、b,他們的shape分別爲:
W:[embeddingSize,attentionSize]W: [embeddingSize, attentionSize]
b:[attentionSize,]b: [attentionSize, ]
h:[attentionSize,]h: [attentionSize, ]
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來把這些向量找出來,最後實現相乘。特徵向量組合的的代碼:
<Vi,Vj>xixj<V_{i},V_{j}>x_{i}x_{j}<Vi,Vj><V_{i},V_{j}>得到的是一個長度爲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爲 Nf(f1)/2KN * f*(f-1)/2 * K f爲fieldSize,k爲embeddingSize。
然後就是計算W<Vi,Vj>xixj+bW<V_{i},V_{j}>x_{i}x_{j}+b,W的shape爲KAK*A,因此首先要把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

因爲WW<Vii,Vj><Vi_{i},V_{j}>相乘是矩陣相乘,所以要遵循矩陣相乘時尺寸的要求。計算完之後再對其進行shape的調整,調整爲NpairsAN*pairs*A
然後就是計算aij=hTReLU(W<Vi,Vj>xixj+b)a_{ij}^{'}=h^{T}ReLU(W<V_{i},V_{j}>x_{i}x_{j}+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

第一行代碼是計算aija_{ij}^{'}第二行代碼是對所有的aija_{ij}^{'}求和,即爲後面的softmax計算aija_{ij}做準備。上面這兩行的keep_dims還是要加的,如果不加的話,在某一維度上進行求和,求和之後得到的結果就會少一個維度。
計算aija_{ij}

        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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章