推薦系統系列(三):FNN理論與實踐


背景

在FM之后出現了很多基於FM的升級改造工作,由於計算復雜度等原因,FM通常只對特征進行二階交叉。當面對海量高度稀疏的用戶行為反饋數據時,二階交叉往往是不夠的,三階、四階甚至更高階的組合交叉能夠進一步提升模型學習能力。如何能在引入更高階的特征組合的同時,將計算復雜度控制在一個可接受的范圍內?

參考圖像領域CNN通過相鄰層連接擴大感受野的做法,使用DNN來對FM顯式表達的二階交叉特征進行再交叉,從而產生更高階的特征組合,加強模型對數據模式的學習能力 [1]。這便是本文所要介紹的FNN(Factorization Machine supported Neural Network)模型,下面將對FNN進行詳細介紹。

分析

1. FNN 結構

FNN的思想比較簡單,直接在FM上接入若干全連接層。利用DNN對特征進行隱式交叉,可以減輕特征工程的工作,同時也能夠將計算時間復雜度控制在一個合理的范圍內。

為了加速模型的收斂,充分利用FM的特征表達能力,FNN采用了兩階段訓練方式。首先,針對任務構建FM模型,完成模型參數的學習。然后,將FM的參數作為FNN底層參數的初始值。這種兩階段方式的應用,是為了將FM作為先驗知識加入到模型中,防止因為數據稀疏帶來的歧義造成模型參數偏差。

However, according to [21], if the observational discriminatory information is highly ambiguous (which is true in our case for ad click behaviour), the posterior weights (from DNN) will not deviate dramatically from the prior (FM).

通過結構圖可以看到,在特征進行輸入之前首先進行分域操作,這種方式也成了后續處理高維稀疏性數據的通用做法,目的是為了減少模型參數量,與FM計算過程保持一致。

模型中的 \(Dense Real Layer\) 將FM產出的低維稠密特征向量進行簡單拼接,作為下一全連接層的輸入,采用 \(tanh\) 激活函數,最終使用 \(sigmoid\) 將輸出壓縮至0~1之間作為預測。

2. 優缺點

優點:

  • 引入DNN對特征進行更高階組合,減少特征工程,能在一定程度上增強FM的學習能力。這種嘗試為后續深度推薦模型的發展提供了新的思路(相比模型效果而言,個人感覺這種融合思路意義更大)。

缺點:

  • 兩階段訓練模式,在應用過程中不方便,且模型能力受限於FM表征能力的上限。
  • FNN專注於高階組合特征,但是卻沒有將低階特征納入模型。

仔細分析下這種兩階段訓練的方式,存在幾個問題

1)FM中進行特征組合,使用的是隱向量點積。將FM得到的隱向量移植到DNN中接入全連接層,全連接本質是將輸入向量的所有元素進行加權求和,且不會對特征Field進行區分,也就是說FNN中高階特征組合使用的是全部隱向量元素相加的方式。說到底,在理解特征組合的層面上FNN與FM是存在Gap的,而這一點也正是PNN對其進行改進的動力。

2)在神經網絡的調參過程中,參數學習率是很重要的。況且FNN中底層參數是通過FM預訓練而來,如果在進行反向傳播更新參數的時候學習率過大,很容易將FM得到的信息抹去。個人理解,FNN至少應該采用Layer-wise learning rate,底層的學習率小一點,上層可以稍微大一點,在保留FM的二階交叉信息的同時,在DNN上層進行更高階的組合。

3. 參數調優

根據論文中的實驗來看,性能影響最大的超參數為:1)DNN部分的網絡結構;2)dropout比例;

個人認為,該論文中超參數對比試驗做的並不嚴謹,以下結論僅供參考。

1)DNN部分的網絡結構

對比四種網絡結構,最佳的網絡結構為 \(Diamond\) .

2)dropout比例

Dropout的效果要比L2正則化更好,且FNN最佳dropout比例為0.8左右。

實驗

依舊使用 \(MovieLens100K dataset\) ,核心代碼如下。

class FNN(object):
    def __init__(self, vec_dim=None, field_lens=None, lr=None, dnn_layers=None, dropout_rate=None, lamda=None):
        self.vec_dim = vec_dim
        self.field_lens = field_lens
        self.field_num = len(field_lens)
        self.lr = lr
        self.dnn_layers = dnn_layers
        self.dropout_rate = dropout_rate
        self.lamda = float(lamda)
        self.l2_reg = tf.contrib.layers.l2_regularizer(self.lamda)

        assert dnn_layers[-1] == 1

        self._build_graph()

    def _build_graph(self):
        self.add_input()
        self.inference()

    def add_input(self):
        self.x = [tf.placeholder(tf.float32, name='input_x_%d'%i) for i in range(self.field_num)]
        self.y = tf.placeholder(tf.float32, shape=[None], name='input_y')
        self.is_train = tf.placeholder(tf.bool)

    def inference(self):
        with tf.variable_scope('fm_part'):
            emb = [tf.get_variable(name='emb_%d'%i, shape=[self.field_lens[i], self.vec_dim], dtype=tf.float32, regularizer=self.l2_reg) for i in range(self.field_num)]
            emb_layer = tf.concat([tf.matmul(self.x[i], emb[i]) for i in range(self.field_num)], axis=1)
        x = emb_layer
        in_node = self.field_num * self.vec_dim
        with tf.variable_scope('dnn_part'):
            for i in range(len(self.dnn_layers)):
                out_node = self.dnn_layers[i]
                w = tf.get_variable(name='w_%d'%i, shape=[in_node, out_node], dtype=tf.float32, regularizer=self.l2_reg)
                b = tf.get_variable(name='b_%d'%i, shape=[out_node], dtype=tf.float32)
                x = tf.matmul(x, w) + b
                if out_node == 1:
                    self.y_logits = x
                else:
                    x = tf.layers.dropout(tf.nn.relu(x), rate=self.dropout_rate, training=self.is_train)
                in_node = out_node


        self.y_hat = tf.nn.sigmoid(self.y_logits)
        self.pred_label = tf.cast(self.y_hat > 0.5, tf.int32)
        self.loss = -tf.reduce_mean(self.y*tf.log(self.y_hat+1e-8) + (1-self.y)*tf.log(1-self.y_hat+1e-8))
        reg_variables = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
        if len(reg_variables) > 0:
            self.loss += tf.add_n(reg_variables)
        self.train_op = tf.train.AdamOptimizer(self.lr).minimize(self.loss)

reference

[1] Zhang, Weinan, Tianming Du, and Jun Wang. "Deep learning over multi-field categorical data." European conference on information retrieval. Springer, Cham, 2016.

知識分享

個人知乎專欄:https://zhuanlan.zhihu.com/c_1164954275573858304

歡迎關注微信公眾號:SOTA Lab
專注知識分享,不定期更新計算機、金融類文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM