1. 程式人生 > 實用技巧 >推薦系統系列(五):Deep Crossing理論與實踐

推薦系統系列(五):Deep Crossing理論與實踐

推薦系統系列(五):Deep Crossing理論與實踐

Dadada 日拱一卒無有盡,功不唐捐終入海

背景

特徵工程是繞不開的話題,巧妙的特徵組合也許能夠為模型帶來質的提升。但同時,特徵工程耗費的資源也是相當可觀的,對於後期模型特徵的維護、模型線上部署不太友好。2016年,微軟提出Deep Crossing模型,旨在解決特徵工程中特徵組合的難題,降低人力特徵組合的時間開銷,通過模型自動學習特徵的組合方式,也能達到不錯的效果,且在各種任務中表現出較好的穩定性。

與之前介紹的FNN、PNN不同的是,Deep Crossing並沒有採用顯式交叉特徵的方式,而是利用殘差網路結構挖掘特徵間的關係。本文將對DeepCrossing從原理到實現細節進行詳細分析。

分析

1. DeepCrossing模型結構

​整個模型包含四種結構:Embedding,Stacking,Residual Unit,Scoring Layer。

論文中使用的目標函式為:,在實際應用中,可以靈活替換為其他目標函式。

下面對各層結構進行分析:

1.1 Embedding & Stacking

Embedding的主要目的是將高維稀疏特徵轉化為低維稠密特徵,其公式化定義為:​ ,其中 ​代表輸入的第​ 個特徵Field,並且已經過one-hot編碼表示,​ 分別表示對應的模型引數。與前幾篇paper介紹的Embedding過程不同的是,DeepCrossing加上了偏置項 ​。公式中的​ 操作等價於使用​ 啟用函式。

儘管可以通過分Field的操作,減少Embedding層的引數量,但是由於某些高基數特徵的存在,如paper中提到的CampaignID,其對應的​ 仍然十分龐大。為此作者提出,針對這些高基數特徵構造衍生特徵,具體操作如下。根據CampaignID的歷史點選率從高到低選擇Top1000個,編號從0到999,將剩餘的ID統一編號為1000。同時構建其衍生特徵,將所有ID對應的歷史點選率組合成1001維的稠密矩陣,各個元素分別為對應ID的歷史CTR,最後一個元素為剩餘ID的平均CTR。通過降維引入衍生特徵的方式,可以有效的減少高基數特徵帶來的引數量劇增問題。

經過Embedding之後,直接對所有的​ 進行拼接Stacking,​ 。作者將特徵embedding為256維,但是對於本身維度低於256的特徵Field,無需進行Embedding,直接送入Stacking層,如上圖中的​ 所示。

1.2 Residual Unit

殘差的網路結構如下:

公式定義為:​

將 ​移項到等式左側,可以看出 ​函式擬合的是輸入與輸出之間的殘差。對輸入進行全連線變換之後,經過​ 啟用函式送入第二個全連線層,將輸出結果與原始輸入進行 element-wise add 操作,再經過​ 啟用輸出。有分析說明,殘差結構能更敏感的捕獲輸入輸出之間的資訊差 [2]。

作者通過各種型別各種大小的實驗發現,DeepCrossing具有很好的魯棒性,推測可能是因為殘差結構能起到類似於正則的效果,但是具體原因是如何的並未明確指出,如果有同學瞭解具體原因,歡迎交流。

1.3 Scoring Layer

使用​ 作為目標函式,可以靈活改用其他函式表示。

2. Early Crossing vs. Late Crossing

在paper中,作者針對特徵交叉的時間點先後的問題進行試驗對比。在DeepCrossing中,特徵是在Embedding之後就開始進行交叉,但是有一些模型如DSSM,是在各類特徵單獨處理完成之後再進行交叉計算,這類模型的結構如下所示:

文中提到,DSSM更擅長文字處理,設計文字處理相關實驗,DeepCrossing比DSSM表現更優異。作者認為,DeepCrossing表現優異主要來源於:1)殘差結構;2)及早的特徵交叉處理;

3. 效能分析

3.1 文字輸入

輸入特徵相同,以DSSM作為baseline,根據Table 3可以看出DeepCrossing相對AUC更高。

將生產環境的已有模型Production作為baseline進行對比,雖然DeepCrossing比DSSM表現更好,但稍遜Production。這是因為Production的訓練資料集不同,且有更為豐富的特徵。

3.2 其他對比

1)衍生特徵Counting Feature的重要性比較

在1.1節中討論過,為了對高基數特徵進行降維處理,引入了統計類衍生特徵(稱之為Counting Feature)。對比此類特徵對於模型的影響,從實驗結果可以看出衍生特徵能夠帶來較大提升。

2)與生產環境模型Production進行比較

使用Production訓練特徵的子集,使用22億條資料進行訓練。最終DeepCrossing表現超過了Production。

實驗

使用​ ,核心程式碼如下。

class DeepCrossing(object):
    def __init__(self, vec_dim=None, field_lens=None, lr=None, residual_unit_num=None, residual_w_dim=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.residual_unit_num = residual_unit_num
        self.residual_w_dim = residual_w_dim
        self.dropout_rate = dropout_rate
        self.lamda = float(lamda)

        self.l2_reg = tf.contrib.layers.l2_regularizer(self.lamda)

        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 _residual_unit(self, input, i):
        x = input
        in_node = self.field_num*self.vec_dim
        out_node = self.residual_w_dim
        w0 = tf.get_variable(name='residual_w0_%d'%i, shape=[in_node, out_node], dtype=tf.float32, regularizer=self.l2_reg)
        b0 = tf.get_variable(name='residual_b0_%d'%i, shape=[out_node], dtype=tf.float32)
        residual = tf.nn.relu(tf.matmul(input, w0) + b0)
        w1 = tf.get_variable(name='residual_w1_%d'%i, shape=[out_node, in_node], dtype=tf.float32, regularizer=self.l2_reg)
        b1 = tf.get_variable(name='residual_b1_%d'%i, shape=[in_node], dtype=tf.float32)
        residual = tf.matmul(residual, w1) + b1
        out = tf.nn.relu(residual+x)
        return out

    def inference(self):
        with tf.variable_scope('emb_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) # (batch, F*K)
        x = emb_layer
        with tf.variable_scope('residual_part'):
            for i in range(self.residual_unit_num):
                x = self._residual_unit(x, i)
                x = tf.layers.dropout(x, rate=self.dropout_rate, training=self.is_train)
        w = tf.get_variable(name='w', shape=[self.field_num*self.vec_dim, 1], dtype=tf.float32, regularizer=self.l2_reg)
        b = tf.get_variable(name='b', shape=[1], dtype=tf.float32)

        self.y_logits = tf.matmul(x, w) + b
        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] Shan, Ying, et al. "Deep crossing: Web-scale modeling without manually crafted combinatorial features."Proceedings of the 22nd ACM SIGKDD international conference on knowledge discovery and data mining. ACM, 2016.

[2]

[3]

歡迎關注公眾號:SOTA Lab

專注知識分享,不定期更新計算機、金融相關文章~

釋出於 2019-11-09