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


背景

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

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

分析

1. DeepCrossing模型結構

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

論文中使用的目標函數為 \(logloss\)\(logloss=-\frac{1}{N}\sum_{i=1}^{N}(y_ilog(p_i)+(1-y_i)log(1-p_i))\) ,在實際應用中,可以靈活替換為其他目標函數。

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

1.1 Embedding & Stacking

Embedding的主要目的是將高維稀疏特征轉化為低維稠密特征,其公式化定義為:\(X_j^O=max(0,W_jX_j^I+b_j)\) ,其中 \(X_j^I\) 代表輸入的第 \(j\) 個特征Field,並且已經過one-hot編碼表示,\(Wb\) 分別表示對應的模型參數。與前幾篇paper介紹的Embedding過程不同的是,DeepCrossing加上了偏置項 \(b\) 。公式中的 \(max\) 操作等價於使用 \(relu\) 激活函數。

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

經過Embedding之后,直接對所有的 \(X_j^O\) 進行拼接Stacking,\(X^O=[X_0^O,X_1^O,\dots,X_K^O]\) 。作者將特征embedding為256維,但是對於本身維度低於256的特征Field,無需進行Embedding,直接送入Stacking層,如上圖中的 \(Feature\#2\) 所示。

1.2 Residual Unit

殘差的網絡結構如下:

公式定義為:$X^O=F(X^I,\{W_0,W_1\},\{b_0,b_1\})+X^I$

\(X^I\) 移項到等式左側,可以看出 \(F\) 函數擬合的是輸入與輸出之間的殘差。對輸入進行全連接變換之后,經過 \(relu\) 激活函數送入第二個全連接層,將輸出結果與原始輸入進行 element-wise add 操作,再經過 \(relu\) 激活輸出。有分析說明,殘差結構能更敏感的捕獲輸入輸出之間的信息差 [2]。

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

1.3 Scoring Layer

使用 \(logloss\) 作為目標函數,可以靈活改用其他函數表示。

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。

實驗

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

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] https://zhuanlan.zhihu.com/p/72679537

[3] https://www.zhihu.com/question/20830906/answer/681688041

知識分享

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

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


免責聲明!

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



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