wide&deep在個性化排序算法中是影響力比較大的工作了。wide部分是手動特征交叉(負責memorization),deep部分利用mlp來實現高階特征交叉(負責generalization),wide部分和deep部分joint train。
Deep&Cross Network模型我們下面將簡稱DCN模型,對比Wide & Deep ,不需要特征工程來獲得高階的交叉特征。對比 FM 系列的模型,DCN 擁有更高的計算效率並且能夠提取到更高階的交叉特征。
一個DCN模型從嵌入和堆積層開始,接着是一個交叉網絡和一個與之平行的深度網絡,之后是最后的組合層,它結合了兩個網絡的輸出。完整的網絡模型如圖:
從網絡結構上面來看,該模型是非常簡單明了的,特征分為類別型與數值型,類別型特征經過 embedding 之后與數值型特征直接拼接作為模型的輸入。所有的特征分別經過 cross 和 deep 網絡,如果把這兩個網絡看作特征提取的話,經過提取后的特征向量拼接之后是常規的二分類,如果訓練數據是曝光和點擊,最后輸出的就可以看作點擊率了。
-
離散特征嵌入
離散特征嵌入這個想法最初來自於 Mikolov 的 word2vec 系列文章。最初解決的問題是詞的獨熱表示過於稀疏,並且不同詞之間的向量形式表示完全沒有聯系。具體思路在此不贅述,最終的實現是將一個上萬維的詞獨熱表示嵌入到了只有幾百維的稠密向量中。而嵌入的本質其實是構建一張隨機初始化的向量查找表,通過我們的訓練目標做有監督學習來得到不同詞在特定目標下,處於向量空間中的位置。
將詞嵌入的思路推廣到其它的離散特征處理中,我們可以用同樣的方法將各種類別特征如“用戶性別”、“城市”、“日期”嵌入到稠密的向量空間中。經過這樣處理之后,自然就解決了原本 FM 遇到的特征稀疏問題。 -
高階交叉特征
在廣告場景下,特征交叉的組合與點擊率是有顯著相關的,例如,“USA”與“Thanksgiving”、“China”與“Chinese New Year”這樣的關聯特征,對用戶的點擊有着正向的影響。
而本文開發了一個新的算子,來得到交叉特征:
即,
考慮 \(x₀\) 為輸入的特征及第一層的輸入,\(x\) 為 第 \(L\) 層的輸入,我們可以看到它的基本思路還是用矩陣乘法來實現特征的組合。
這是個遞推形式算子,所以使用它很容易能得到高於二階的交叉特征;並且該模型還用了殘差的思想,解決網絡性能退化的問題;此公式還有一個小的優化技巧,三矩陣相乘那個算子,用乘法結合律先計算后面兩個矩陣的積,這樣可以減少三分之一的計算復雜度。
DCN實現
參考個性化排序算法實踐(三)——deepFM算法,算法主框架與其類似,重點講述DCN的實現步驟。
模型輸入
模型的輸入主要有下面幾個部分:
self.feat_index = tf.placeholder(tf.int32,
shape=[None,None],
name='feat_index')
self.feat_value = tf.placeholder(tf.float32,
shape=[None,None],
name='feat_value')
self.numeric_value = tf.placeholder(tf.float32,[None,None],name='num_value')
self.label = tf.placeholder(tf.float32,shape=[None,1],name='label')
self.dropout_keep_deep = tf.placeholder(tf.float32,shape=[None],name='dropout_deep_deep')
可以看到,這里與DeepFM相比,一個明顯的變化是將離散特征和連續特征分開,連續特征不在轉換成embedding進行輸入,所以我們的輸入共有五部分。
feat_index是離散特征的一個序號,主要用於通過embedding_lookup選擇我們的embedding。feat_value是對應離散特征的特征值。numeric_value是我們的連續特征值。label是實際值。還定義了兩個dropout來防止過擬合。
權重構建
權重主要包含四部分,embedding層的權重,cross network中的權重,deep network中的權重以及最后鏈接層的權重,我們使用一個字典來表示:
def _initialize_weights(self):
weights = dict()
#embeddings
weights['feature_embeddings'] = tf.Variable(
tf.random_normal([self.cate_feature_size,self.embedding_size],0.0,0.01),
name='feature_embeddings')
weights['feature_bias'] = tf.Variable(tf.random_normal([self.cate_feature_size,1],0.0,1.0),name='feature_bias')
#deep layers
num_layer = len(self.deep_layers)
glorot = np.sqrt(2.0/(self.total_size + self.deep_layers[0]))
weights['deep_layer_0'] = tf.Variable(
np.random.normal(loc=0,scale=glorot,size=(self.total_size,self.deep_layers[0])),dtype=np.float32
)
weights['deep_bias_0'] = tf.Variable(
np.random.normal(loc=0,scale=glorot,size=(1,self.deep_layers[0])),dtype=np.float32
)
for i in range(1,num_layer):
glorot = np.sqrt(2.0 / (self.deep_layers[i - 1] + self.deep_layers[i]))
weights["deep_layer_%d" % i] = tf.Variable(
np.random.normal(loc=0, scale=glorot, size=(self.deep_layers[i - 1], self.deep_layers[i])),
dtype=np.float32) # layers[i-1] * layers[i]
weights["deep_bias_%d" % i] = tf.Variable(
np.random.normal(loc=0, scale=glorot, size=(1, self.deep_layers[i])),
dtype=np.float32) # 1 * layer[i]
for i in range(self.cross_layer_num):
weights["cross_layer_%d" % i] = tf.Variable(
np.random.normal(loc=0, scale=glorot, size=(self.total_size,1)),
dtype=np.float32)
weights["cross_bias_%d" % i] = tf.Variable(
np.random.normal(loc=0, scale=glorot, size=(self.total_size,1)),
dtype=np.float32) # 1 * layer[i]
# final concat projection layer
input_size = self.total_size + self.deep_layers[-1]
glorot = np.sqrt(2.0/(input_size + 1))
weights['concat_projection'] = tf.Variable(np.random.normal(loc=0,scale=glorot,size=(input_size,1)),dtype=np.float32)
weights['concat_bias'] = tf.Variable(tf.constant(0.01),dtype=np.float32)
return weights
計算網絡輸入
這一塊我們要計算兩個並行網絡的輸入\(X_0\),我們需要將離散特征轉換成embedding,同時拼接上連續特征:
# model
self.embeddings = tf.nn.embedding_lookup(self.weights['feature_embeddings'],self.feat_index) # N * F * K
feat_value = tf.reshape(self.feat_value,shape=[-1,self.field_size,1])
self.embeddings = tf.multiply(self.embeddings,feat_value)
self.x0 = tf.concat([self.numeric_value,
tf.reshape(self.embeddings,shape=[-1,self.field_size * self.embedding_size])]
,axis=1)
Cross Network
根據論文中的計算公式,一步步計算得到cross network的輸出:
# cross_part
self._x0 = tf.reshape(self.x0, (-1, self.total_size, 1))
x_l = self._x0
for l in range(self.cross_layer_num):
#x_l = tf.tensordot(tf.matmul(self._x0, x_l, transpose_b=True),self.weights["cross_layer_%d" % l],1) + self.weights["cross_bias_%d" % l] + x_l
# 注意計算順序,可以加速很多
x_l = tf.tensordot(tf.reshape(x_l, [-1, 1, self.total_size]), self.weights["cross_layer_%d" % l], 1) * self._x0 + self.weights["cross_bias_%d" % l] + x_l
self.cross_network_out = tf.reshape(x_l, (-1, self.total_size))
Deep Network
這一塊就是一個多層全鏈接神經網絡:
self.y_deep = tf.nn.dropout(self.x0,self.dropout_keep_deep[0])
for i in range(0,len(self.deep_layers)):
self.y_deep = tf.add(tf.matmul(self.y_deep,self.weights["deep_layer_%d" %i]), self.weights["deep_bias_%d"%I])
self.y_deep = self.deep_layers_activation(self.y_deep)
self.y_deep = tf.nn.dropout(self.y_deep,self.dropout_keep_deep[i+1])
Combination Layer
最后將兩個網絡的輸出拼接起來,經過一層全鏈接得到最終的輸出:
# concat_part
concat_input = tf.concat([self.cross_network_out, self.y_deep], axis=1)
self.out = tf.add(tf.matmul(concat_input,self.weights['concat_projection']),self.weights['concat_bias'])
定義損失
這里我們可以選擇logloss或者mse,並加上L2正則項:
# loss
if self.loss_type == "logloss":
self.out = tf.nn.sigmoid(self.out)
self.loss = tf.losses.log_loss(self.label, self.out)
elif self.loss_type == "mse":
self.loss = tf.nn.l2_loss(tf.subtract(self.label, self.out))
# l2 regularization on weights
if self.l2_reg > 0:
self.loss += tf.contrib.layers.l2_regularizer(
self.l2_reg)(self.weights["concat_projection"])
for i in range(len(self.deep_layers)):
self.loss += tf.contrib.layers.l2_regularizer(
self.l2_reg)(self.weights["deep_layer_%d" % I])
for i in range(self.cross_layer_num):
self.loss += tf.contrib.layers.l2_regularizer(
self.l2_reg)(self.weights["cross_layer_%d" % I])
DCN和同場景模型對比
在deepFM中, 進行了離散特征嵌入的操作,並且還將嵌入前的離散特征加入到了 FM 層;所以該網絡可以看作是傳統的 FM 、離散特征嵌入之后的 FM 和基本 DNN 三個模型融合的結果。
wide & deep 的思路中,deep 部分的做法和 deepFM 是大相徑庭的,關鍵的 wide 部分其實是離線的特征工程,根據業務場景提前完成了特征交叉等處理,該模型可以看作是 DNN 與離線特征模型的融合結果。
而從 DCN 的網絡中我們可以發現,deep 部分網絡除了使用離散嵌入特征外,還拼接了數值型特征;cross 部分網絡直接完成了特征組合,對比 FM 層它可以學到更高階的組合特征,對比 wide 網絡它不需要做線下的特征工程。
參考:
深度排序模型概述(一)Wide&Deep/xDeepFM
推薦系統遇上深度學習(五)--Deep&Cross Network模型
Github