从FM推演各深度学习CTR预估模型
本文的PDF版本、代碼實(shí)現(xiàn)和數(shù)據(jù)可以在我的github取到。
1.引言
點(diǎn)擊率(click-through rate, CTR)是互聯(lián)網(wǎng)公司進(jìn)行流量分配的核心依據(jù)之一。比如互聯(lián)網(wǎng)廣告平臺,為了精細(xì)化權(quán)衡和保障用戶、廣告、平臺三方的利益,準(zhǔn)確的CTR預(yù)估是不可或缺的。CTR預(yù)估技術(shù)從傳統(tǒng)的邏輯回歸,到近兩年大火的深度學(xué)習(xí),新的算法層出不窮:DeepFM, NFM, DIN, AFM, DCN……?
然而,相關(guān)的綜述文章不少,但碎片羅列的居多,模型之間內(nèi)在的聯(lián)系和演化思路如何揭示?怎樣才能迅速get到新模型的創(chuàng)新點(diǎn)和適用場景,快速提高新論文速度,節(jié)約理解、復(fù)現(xiàn)模型的成本?這些都是亟待解決的問題。?
我們認(rèn)為,從FM及其與神經(jīng)網(wǎng)絡(luò)的結(jié)合出發(fā),能夠迅速貫穿很多深度學(xué)習(xí)CTR預(yù)估網(wǎng)絡(luò)的思路,從而更好地理解和應(yīng)用模型。
2.本文的思路與方法
3.FM:降維版本的特征二階組合
CTR預(yù)估本質(zhì)是一個二分類問題,以移動端展示廣告推薦為例,依據(jù)日志中的用戶側(cè)的信息(比如年齡,性別,國籍,手機(jī)上安裝的app列表)、廣告?zhèn)鹊男畔?#xff08;廣告id,廣告類別,廣告標(biāo)題等)、上下文側(cè)信息(渠道id等),去建模預(yù)測用戶是否會點(diǎn)擊該廣告。?
FM出現(xiàn)之前的傳統(tǒng)的處理方法是人工特征工程加上線性模型(如邏輯回歸Logistic Regression)。為了提高模型效果,關(guān)鍵技術(shù)是找到到用戶點(diǎn)擊行為背后隱含的特征組合。如男性、大學(xué)生用戶往往會點(diǎn)擊游戲類廣告,因此“男性且是大學(xué)生且是游戲類”的特征組合就是一個關(guān)鍵特征。但這本質(zhì)仍是線性模型,其假設(shè)函數(shù)表示成內(nèi)積形式一般為:
?
ylinear=σ(?w??,x???)ylinear=σ(?w→,x→?)
?
其中x??x→為特征向量,w??w→為權(quán)重向量,σ()σ()為sigmoid函數(shù)。
但是人工進(jìn)行特征組合通常會存在諸多困難,如特征爆炸、特征難以被識別、組合特征難以設(shè)計(jì)等。為了讓模型自動地考慮特征之間的二階組合信息,線性模型推廣為二階多項(xiàng)式(2d?Polynomial2d?Polynomial)模型:
?
ypoly=σ(?w??,x???+∑i=1n∑j=1nwij?xi?xj)ypoly=σ(?w→,x→?+∑i=1n∑j=1nwij?xi?xj)
?
其實(shí)就是對特征兩兩相乘(組合)構(gòu)成新特征(離散化之后其實(shí)就是“且”操作),并對每個新特征分配獨(dú)立的權(quán)重,通過機(jī)器學(xué)習(xí)來自動得到這些權(quán)重。將其寫成矩陣形式為:
?
ypoly=σ(w??T?x??+x??T?W(2)?x??)ypoly=σ(w→T?x→+x→T?W(2)?x→)
?
其中W(2)W(2)為二階特征組合的權(quán)重矩陣,是對稱矩陣。而這個矩陣參數(shù)非常多,為O(n2)O(n2)。為了降低該矩陣的維度,可以將其因子分解(FactorizationFactorization)為兩個低維(比如n?kn?k)矩陣的相乘。則此時(shí)WW矩陣的參數(shù)就大幅降低,為O(nk)O(nk)。公式如下:
?
W(2)=WT?WW(2)=WT?W
?
這就是RendleRendle等在2010年提出因子分解機(jī)(Factorization Machines,FM)的名字的由來。FM的矩陣形式公式如下:
?
yFM=σ(w??T?x??+x??T?WT?W?x??)yFM=σ(w→T?x→+x→T?WT?W?x→)
?
將其寫成內(nèi)積的形式:?
yFM=σ(?w??,x???+?W?x??,W?x???)yFM=σ(?w→,x→?+?W?x→,W?x→?)
利用?∑ni=1ai→,∑ni=1ai→?=∑ni=1∑nj=1?ai→,aj→??∑i=1nai→,∑i=1nai→?=∑i=1n∑j=1n?ai→,aj→?,可以將上式進(jìn)一步改寫成求和式的形式:?
yFM=σ(?w??,x???+∑i=1n∑j=1n?xi?v??i,xj?v??j?)yFM=σ(?w→,x→?+∑i=1n∑j=1n?xi?v→i,xj?v→j?)
其中vi→vi→向量是矩陣WW的第ii列。為了去除重復(fù)項(xiàng)與特征平方項(xiàng),上式可以進(jìn)一步改寫成更為常見的FM公式:?
yFM=σ(?w??,x???+∑i=1n∑j=i+1n?v??i,v??j?xi?xj)yFM=σ(?w→,x→?+∑i=1n∑j=i+1n?v→i,v→j?xi?xj)
對比二階多項(xiàng)式模型,FM模型中特征兩兩相乘(組合)的權(quán)重是相互不獨(dú)立的,它是一種參數(shù)較少但表達(dá)力強(qiáng)的模型。
?
此處附上FM的TensorFlow代碼實(shí)現(xiàn),完整數(shù)據(jù)和代碼請戳這里。注意FM通過內(nèi)積進(jìn)行無重復(fù)項(xiàng)與特征平方項(xiàng)的特征組合過程使用了一個小trick,就是:?
∑i=1n∑j=i+1nxixj=1/2×[(∑i=1nxi)2?∑i=1nx2i]∑i=1n∑j=i+1nxixj=1/2×[(∑i=1nxi)2?∑i=1nxi2]
?
class FM(Model):def __init__(self, input_dim=None, output_dim=1, factor_order=10, init_path=None, opt_algo='gd', learning_rate=1e-2,l2_w=0, l2_v=0, random_seed=None):Model.__init__(self)# 一次、二次交叉、偏置項(xiàng)init_vars = [('w', [input_dim, output_dim], 'xavier', dtype),('v', [input_dim, factor_order], 'xavier', dtype),('b', [output_dim], 'zero', dtype)]self.graph = tf.Graph()with self.graph.as_default():if random_seed is not None:tf.set_random_seed(random_seed)self.X = tf.sparse_placeholder(dtype)self.y = tf.placeholder(dtype)self.vars = init_var_map(init_vars, init_path)w = self.vars['w']v = self.vars['v']b = self.vars['b']# [(x1+x2+x3)^2 - (x1^2+x2^2+x3^2)]/2# 先計(jì)算所有的交叉項(xiàng),再減去平方項(xiàng)(自己和自己相乘)X_square = tf.SparseTensor(self.X.indices, tf.square(self.X.values), tf.to_int64(tf.shape(self.X)))xv = tf.square(tf.sparse_tensor_dense_matmul(self.X, v))p = 0.5 * tf.reshape(tf.reduce_sum(xv - tf.sparse_tensor_dense_matmul(X_square, tf.square(v)), 1),[-1, output_dim])xw = tf.sparse_tensor_dense_matmul(self.X, w)logits = tf.reshape(xw + b + p, [-1])self.y_prob = tf.sigmoid(logits)self.loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=logits, labels=self.y)) + \l2_w * tf.nn.l2_loss(xw) + \l2_v * tf.nn.l2_loss(xv)self.optimizer = get_optimizer(opt_algo, learning_rate, self.loss)#GPU設(shè)定config = tf.ConfigProto()config.gpu_options.allow_growth = Trueself.sess = tf.Session(config=config)# 圖中所有variable初始化tf.global_variables_initializer().run(session=self.sess)- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
4.用神經(jīng)網(wǎng)絡(luò)的視角看FM:嵌入后再進(jìn)行內(nèi)積
我們觀察FM公式的矩陣內(nèi)積形式:
?
yFM=σ(?w??,x???+?W?x??,W?x???)yFM=σ(?w→,x→?+?W?x→,W?x→?)
?
發(fā)現(xiàn)W?x??W·x→部分就是將離散系數(shù)特征通過矩陣乘法降維成一個低維稠密向量。這個過程對神經(jīng)網(wǎng)絡(luò)來說就叫做嵌入(embedding)。所以用神經(jīng)網(wǎng)絡(luò)視角來看:
其示意圖如下。為了表述清晰,我們繪制的是神經(jīng)網(wǎng)絡(luò)計(jì)算圖而不是網(wǎng)絡(luò)結(jié)構(gòu)圖——在網(wǎng)絡(luò)結(jié)構(gòu)圖中增加了權(quán)重WW的位置。?
5.FM的實(shí)際應(yīng)用:考慮領(lǐng)域信息
廣告點(diǎn)擊率預(yù)估模型中的特征以分領(lǐng)域的離散特征為主,如:廣告類別、用戶職業(yè)、手機(jī)APP列表等。由于連續(xù)特征比較好處理,為了簡化起見,本文只考慮同時(shí)存在不同領(lǐng)域的離散特征的情形。處理離散特征的常見方法是通過獨(dú)熱(one-hot)編碼轉(zhuǎn)換為一系列二值特征向量。然后將這些高維稀疏特征通過嵌入(embedding)轉(zhuǎn)換為低維連續(xù)特征。前面已經(jīng)說明FM中間的一個核心步驟就是嵌入,但這個嵌入過程沒有考慮領(lǐng)域信息。這使得同領(lǐng)域內(nèi)的特征也被當(dāng)做不同領(lǐng)域特征進(jìn)行兩兩組合了。?
其實(shí)可以將特征具有領(lǐng)域關(guān)系的特點(diǎn)作為先驗(yàn)知識加入到神經(jīng)網(wǎng)絡(luò)的設(shè)計(jì)中去:同領(lǐng)域的特征嵌入后直接求和作為一個整體嵌入向量,進(jìn)而與其他領(lǐng)域的整體嵌入向量進(jìn)行兩兩組合。而這個先嵌入后求和的過程,就是一個單領(lǐng)域的小離散特征向量乘以矩陣的過程。此時(shí)FM的過程變?yōu)?#xff1a;對不同領(lǐng)域的離散特征分別進(jìn)行嵌入,之后再進(jìn)行二階特征的向量內(nèi)積。其計(jì)算圖圖如下所示:?
這樣考慮其實(shí)是給FM增加了一個正則:考慮了領(lǐng)域內(nèi)的信息的相似性。而且還有一個附加的好處,這些嵌入后的同領(lǐng)域特征可以拼接起來作為更深的神經(jīng)網(wǎng)絡(luò)的輸入,達(dá)到降維的目的。接下來我們將反復(fù)看到這種處理方式。?
此處需要注意,這與“基于領(lǐng)域的因子分解機(jī)”(Field-aware Factorization Machines,FFM)有區(qū)別。FFM也是FM的另一種變體,也考慮了領(lǐng)域信息。但其不同點(diǎn)是同一個特征與不同領(lǐng)域進(jìn)行特征組合時(shí),其對應(yīng)的嵌入向量是不同的。本文不考慮FFM的作用機(jī)制。?
經(jīng)過這些改進(jìn)的FM終究還是淺層網(wǎng)絡(luò),它的表現(xiàn)力仍然有限。為了增加模型的表現(xiàn)力(model capacity),一個自然的想法就是將該淺層網(wǎng)絡(luò)不斷“深化”。
?
6.embedding+MLP:深度學(xué)習(xí)CTR預(yù)估的通用框架
embedding+MLP是對于分領(lǐng)域離散特征進(jìn)行深度學(xué)習(xí)CTR預(yù)估的通用框架。深度學(xué)習(xí)在特征組合挖掘(特征學(xué)習(xí))方面具有很大的優(yōu)勢。比如以CNN為代表的深度網(wǎng)絡(luò)主要用于圖像、語音等稠密特征上的學(xué)習(xí),以W2V、RNN為代表的深度網(wǎng)絡(luò)主要用于文本的同質(zhì)化、序列化高維稀疏特征的學(xué)習(xí)。CTR預(yù)估的主要場景是對離散且有具體領(lǐng)域的特征進(jìn)行學(xué)習(xí),所以其深度網(wǎng)絡(luò)結(jié)構(gòu)也不同于CNN與RNN。?
具體來說, embedding+MLP的過程如下:
其示意圖如下:?
embedding+MLP的缺點(diǎn)是只學(xué)習(xí)高階特征組合,對于低階或者手動的特征組合不夠兼容,而且參數(shù)較多,學(xué)習(xí)較困難。
7.FNN:FM與MLP的串聯(lián)結(jié)合
Weinan Zhang等在2016年提出的因子分解機(jī)神經(jīng)網(wǎng)絡(luò)(Factorisation Machine supported Neural Network,FNN)將考FM與MLP進(jìn)行了結(jié)合。它有著十分顯著的特點(diǎn):
其計(jì)算圖如下所示:?
通過觀察FFN的計(jì)算圖可以看出其與embedding+MLP確實(shí)非常像。不過此處省略了FNN的FM部分的線性模塊。這種省略為了更好地進(jìn)行兩個模型的對比。接下來的計(jì)算圖我們都會省略線性模塊。
此處附上FNN的代碼實(shí)現(xiàn),完整數(shù)據(jù)和代碼請戳這里。:
class FNN(Model):def __init__(self, field_sizes=None, embed_size=10, layer_sizes=None, layer_acts=None, drop_out=None,embed_l2=None, layer_l2=None, init_path=None, opt_algo='gd', learning_rate=1e-2, random_seed=None):Model.__init__(self)init_vars = []num_inputs = len(field_sizes)for i in range(num_inputs):init_vars.append(('embed_%d' % i, [field_sizes[i], embed_size], 'xavier', dtype))node_in = num_inputs * embed_sizefor i in range(len(layer_sizes)):init_vars.append(('w%d' % i, [node_in, layer_sizes[i]], 'xavier', dtype))init_vars.append(('b%d' % i, [layer_sizes[i]], 'zero', dtype))node_in = layer_sizes[i]self.graph = tf.Graph()with self.graph.as_default():if random_seed is not None:tf.set_random_seed(random_seed)self.X = [tf.sparse_placeholder(dtype) for i in range(num_inputs)]self.y = tf.placeholder(dtype)self.keep_prob_train = 1 - np.array(drop_out)self.keep_prob_test = np.ones_like(drop_out)self.layer_keeps = tf.placeholder(dtype)self.vars = init_var_map(init_vars, init_path)w0 = [self.vars['embed_%d' % i] for i in range(num_inputs)]xw = tf.concat([tf.sparse_tensor_dense_matmul(self.X[i], w0[i]) for i in range(num_inputs)], 1)l = xw#全連接部分for i in range(len(layer_sizes)):wi = self.vars['w%d' % i]bi = self.vars['b%d' % i]print(l.shape, wi.shape, bi.shape)l = tf.nn.dropout(activate(tf.matmul(l, wi) + bi,layer_acts[i]),self.layer_keeps[i])l = tf.squeeze(l)self.y_prob = tf.sigmoid(l)self.loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=l, labels=self.y))if layer_l2 is not None:self.loss += embed_l2 * tf.nn.l2_loss(xw)for i in range(len(layer_sizes)):wi = self.vars['w%d' % i]self.loss += layer_l2[i] * tf.nn.l2_loss(wi)self.optimizer = get_optimizer(opt_algo, learning_rate, self.loss)config = tf.ConfigProto()config.gpu_options.allow_growth = Trueself.sess = tf.Session(config=config)tf.global_variables_initializer().run(session=self.sess)- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
8.DeepFM: FM與MLP的并聯(lián)結(jié)合
針對FNN需要預(yù)訓(xùn)練的問題,Huifeng Guo等提出了深度因子分解機(jī)模型(Deep Factorisation Machine, DeepFM, 2017)。該模型的特點(diǎn)是:
其計(jì)算圖如下所示:?
通過觀察DeepFM的計(jì)算圖可以看出紅色虛線以上部分其實(shí)就是FM部分,虛線以下就是MLP部分。
此處附上DeepFM的代碼實(shí)現(xiàn),完整數(shù)據(jù)和代碼請戳這里。:
def model_fn(features, labels, mode, params):"""Bulid Model function f(x) for Estimator."""#------超參數(shù)的設(shè)定----field_size = params["field_size"]feature_size = params["feature_size"]embedding_size = params["embedding_size"]l2_reg = params["l2_reg"]learning_rate = params["learning_rate"]#batch_norm_decay = params["batch_norm_decay"]#optimizer = params["optimizer"]layers = map(int, params["deep_layers"].split(','))dropout = map(float, params["dropout"].split(','))#------權(quán)重------FM_B = tf.get_variable(name='fm_bias', shape=[1], initializer=tf.constant_initializer(0.0))FM_W = tf.get_variable(name='fm_w', shape=[feature_size], initializer=tf.glorot_normal_initializer())# FFM_V = tf.get_variable(name='fm_v', shape=[feature_size, embedding_size], initializer=tf.glorot_normal_initializer())# F * E #------build feaure-------feat_ids = features['feat_ids']feat_ids = tf.reshape(feat_ids,shape=[-1,field_size]) # None * f/K * Kfeat_vals = features['feat_vals']feat_vals = tf.reshape(feat_vals,shape=[-1,field_size]) # None * f/K * K#------build f(x)------with tf.variable_scope("First-order"):feat_wgts = tf.nn.embedding_lookup(FM_W, feat_ids) # None * f/K * Ky_w = tf.reduce_sum(tf.multiply(feat_wgts, feat_vals),1)with tf.variable_scope("Second-order"):embeddings = tf.nn.embedding_lookup(FM_V, feat_ids) # None * f/K * K * Efeat_vals = tf.reshape(feat_vals, shape=[-1, field_size, 1]) # None * f/K * K * 1 ?embeddings = tf.multiply(embeddings, feat_vals) #vij*xi sum_square = tf.square(tf.reduce_sum(embeddings,1)) # None * K * Esquare_sum = tf.reduce_sum(tf.square(embeddings),1)y_v = 0.5*tf.reduce_sum(tf.subtract(sum_square, square_sum),1) # None * 1with tf.variable_scope("Deep-part"):if FLAGS.batch_norm:#normalizer_fn = tf.contrib.layers.batch_norm#normalizer_fn = tf.layers.batch_normalizationif mode == tf.estimator.ModeKeys.TRAIN:train_phase = True#normalizer_params = {'decay': batch_norm_decay, 'center': True, 'scale': True, 'updates_collections': None, 'is_training': True, 'reuse': None}else:train_phase = False#normalizer_params = {'decay': batch_norm_decay, 'center': True, 'scale': True, 'updates_collections': None, 'is_training': False, 'reuse': True}else:normalizer_fn = Nonenormalizer_params = Nonedeep_inputs = tf.reshape(embeddings,shape=[-1,field_size*embedding_size]) # None * (F*K)for i in range(len(layers)):#if FLAGS.batch_norm:# deep_inputs = batch_norm_layer(deep_inputs, train_phase=train_phase, scope_bn='bn_%d' %i)#normalizer_params.update({'scope': 'bn_%d' %i})deep_inputs = tf.contrib.layers.fully_connected(inputs=deep_inputs, num_outputs=layers[i], \#normalizer_fn=normalizer_fn, normalizer_params=normalizer_params, \weights_regularizer=tf.contrib.layers.l2_regularizer(l2_reg), scope='mlp%d' % i)if FLAGS.batch_norm:deep_inputs = batch_norm_layer(deep_inputs, train_phase=train_phase, scope_bn='bn_%d' %i) #放在RELU之后 https://github.com/ducha-aiki/caffenet-benchmark/blob/master/batchnorm.md#bn----before-or-after-reluif mode == tf.estimator.ModeKeys.TRAIN:deep_inputs = tf.nn.dropout(deep_inputs, keep_prob=dropout[i]) #Apply Dropout after all BN layers and set dropout=0.8(drop_ratio=0.2)#deep_inputs = tf.layers.dropout(inputs=deep_inputs, rate=dropout[i], training=mode == tf.estimator.ModeKeys.TRAIN)y_deep = tf.contrib.layers.fully_connected(inputs=deep_inputs, num_outputs=1, activation_fn=tf.identity, \weights_regularizer=tf.contrib.layers.l2_regularizer(l2_reg), scope='deep_out')y_d = tf.reshape(y_deep,shape=[-1])#sig_wgts = tf.get_variable(name='sigmoid_weights', shape=[layers[-1]], initializer=tf.glorot_normal_initializer())#sig_bias = tf.get_variable(name='sigmoid_bias', shape=[1], initializer=tf.constant_initializer(0.0))#deep_out = tf.nn.xw_plus_b(deep_inputs,sig_wgts,sig_bias,name='deep_out')with tf.variable_scope("DeepFM-out"):#y_bias = FM_B * tf.ones_like(labels, dtype=tf.float32) # None * 1 warning;這里不能用label,否則調(diào)用predict/export函數(shù)會出錯,train/evaluate正常;初步判斷estimator做了優(yōu)化,用不到label時(shí)不傳y_bias = FM_B * tf.ones_like(y_d, dtype=tf.float32) # None * 1y = y_bias + y_w + y_v + y_dpred = tf.sigmoid(y)predictions={"prob": pred}export_outputs = {tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: tf.estimator.export.PredictOutput(predictions)}# Provide an estimator spec for `ModeKeys.PREDICT`if mode == tf.estimator.ModeKeys.PREDICT:return tf.estimator.EstimatorSpec(mode=mode,predictions=predictions,export_outputs=export_outputs)#------bulid loss------loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=y, labels=labels)) + \l2_reg * tf.nn.l2_loss(FM_W) + \l2_reg * tf.nn.l2_loss(FM_V) #+ \ l2_reg * tf.nn.l2_loss(sig_wgts)# Provide an estimator spec for `ModeKeys.EVAL`eval_metric_ops = {"auc": tf.metrics.auc(labels, pred)}if mode == tf.estimator.ModeKeys.EVAL:return tf.estimator.EstimatorSpec(mode=mode,predictions=predictions,loss=loss,eval_metric_ops=eval_metric_ops)#------bulid optimizer------if FLAGS.optimizer == 'Adam':optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate, beta1=0.9, beta2=0.999, epsilon=1e-8)elif FLAGS.optimizer == 'Adagrad':optimizer = tf.train.AdagradOptimizer(learning_rate=learning_rate, initial_accumulator_value=1e-8)elif FLAGS.optimizer == 'Momentum':optimizer = tf.train.MomentumOptimizer(learning_rate=learning_rate, momentum=0.95)elif FLAGS.optimizer == 'ftrl':optimizer = tf.train.FtrlOptimizer(learning_rate)train_op = optimizer.minimize(loss, global_step=tf.train.get_global_step())# Provide an estimator spec for `ModeKeys.TRAIN` modesif mode == tf.estimator.ModeKeys.TRAIN:return tf.estimator.EstimatorSpec(mode=mode,predictions=predictions,loss=loss,train_op=train_op)- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
9.NFM:通過逐元素乘法延遲FM的實(shí)現(xiàn)過程
我們再回到考慮領(lǐng)域信息的FM,它仍有改進(jìn)的空間。因?yàn)橐陨线@些網(wǎng)絡(luò)的FM部分都是只進(jìn)行嵌入向量的兩兩內(nèi)積后直接求和,沒有充分利用二階特征組合的信息。Xiangnan He等在2017年提出了神經(jīng)網(wǎng)絡(luò)因子分解機(jī)(Neural Factorization Machines,NFM)對此作出了改進(jìn)。其計(jì)算圖如下所示:?
?
NFM的基本特點(diǎn)是:
此處附上NFM的代碼實(shí)現(xiàn),完整數(shù)據(jù)和代碼請戳這里:
def model_fn(features, labels, mode, params):"""Bulid Model function f(x) for Estimator."""#------hyperparameters----field_size = params["field_size"]feature_size = params["feature_size"]embedding_size = params["embedding_size"]l2_reg = params["l2_reg"]learning_rate = params["learning_rate"]#optimizer = params["optimizer"]layers = map(int, params["deep_layers"].split(','))dropout = map(float, params["dropout"].split(','))#------bulid weights------Global_Bias = tf.get_variable(name='bias', shape=[1], initializer=tf.constant_initializer(0.0))Feat_Bias = tf.get_variable(name='linear', shape=[feature_size], initializer=tf.glorot_normal_initializer())Feat_Emb = tf.get_variable(name='emb', shape=[feature_size,embedding_size], initializer=tf.glorot_normal_initializer())#------build feaure-------feat_ids = features['feat_ids']feat_ids = tf.reshape(feat_ids,shape=[-1,field_size])feat_vals = features['feat_vals']feat_vals = tf.reshape(feat_vals,shape=[-1,field_size])#------build f(x)------with tf.variable_scope("Linear-part"):feat_wgts = tf.nn.embedding_lookup(Feat_Bias, feat_ids) # None * F * 1y_linear = tf.reduce_sum(tf.multiply(feat_wgts, feat_vals),1)with tf.variable_scope("BiInter-part"):embeddings = tf.nn.embedding_lookup(Feat_Emb, feat_ids) # None * F * Kfeat_vals = tf.reshape(feat_vals, shape=[-1, field_size, 1])embeddings = tf.multiply(embeddings, feat_vals) # vij * xisum_square_emb = tf.square(tf.reduce_sum(embeddings,1))square_sum_emb = tf.reduce_sum(tf.square(embeddings),1)deep_inputs = 0.5*tf.subtract(sum_square_emb, square_sum_emb) # None * Kwith tf.variable_scope("Deep-part"):if mode == tf.estimator.ModeKeys.TRAIN:train_phase = Trueelse:train_phase = Falseif mode == tf.estimator.ModeKeys.TRAIN:deep_inputs = tf.nn.dropout(deep_inputs, keep_prob=dropout[0]) # None * Kfor i in range(len(layers)):deep_inputs = tf.contrib.layers.fully_connected(inputs=deep_inputs, num_outputs=layers[i], \weights_regularizer=tf.contrib.layers.l2_regularizer(l2_reg), scope='mlp%d' % i)if FLAGS.batch_norm:deep_inputs = batch_norm_layer(deep_inputs, train_phase=train_phase, scope_bn='bn_%d' %i) #放在RELU之后 https://github.com/ducha-aiki/caffenet-benchmark/blob/master/batchnorm.md#bn----before-or-after-reluif mode == tf.estimator.ModeKeys.TRAIN:deep_inputs = tf.nn.dropout(deep_inputs, keep_prob=dropout[i]) #Apply Dropout after all BN layers and set dropout=0.8(drop_ratio=0.2)#deep_inputs = tf.layers.dropout(inputs=deep_inputs, rate=dropout[i], training=mode == tf.estimator.ModeKeys.TRAIN)y_deep = tf.contrib.layers.fully_connected(inputs=deep_inputs, num_outputs=1, activation_fn=tf.identity, \weights_regularizer=tf.contrib.layers.l2_regularizer(l2_reg), scope='deep_out')y_d = tf.reshape(y_deep,shape=[-1])with tf.variable_scope("NFM-out"):#y_bias = Global_Bias * tf.ones_like(labels, dtype=tf.float32) # None * 1 warning;這里不能用label,否則調(diào)用predict/export函數(shù)會出錯,train/evaluate正常;初步判斷estimator做了優(yōu)化,用不到label時(shí)不傳y_bias = Global_Bias * tf.ones_like(y_d, dtype=tf.float32) # None * 1y = y_bias + y_linear + y_dpred = tf.sigmoid(y)predictions={"prob": pred}export_outputs = {tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: tf.estimator.export.PredictOutput(predictions)}# Provide an estimator spec for `ModeKeys.PREDICT`if mode == tf.estimator.ModeKeys.PREDICT:return tf.estimator.EstimatorSpec(mode=mode,predictions=predictions,export_outputs=export_outputs)#------bulid loss------loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=y, labels=labels)) + \l2_reg * tf.nn.l2_loss(Feat_Bias) + l2_reg * tf.nn.l2_loss(Feat_Emb)# Provide an estimator spec for `ModeKeys.EVAL`eval_metric_ops = {"auc": tf.metrics.auc(labels, pred)}if mode == tf.estimator.ModeKeys.EVAL:return tf.estimator.EstimatorSpec(mode=mode,predictions=predictions,loss=loss,eval_metric_ops=eval_metric_ops)#------bulid optimizer------if FLAGS.optimizer == 'Adam':optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate, beta1=0.9, beta2=0.999, epsilon=1e-8)elif FLAGS.optimizer == 'Adagrad':optimizer = tf.train.AdagradOptimizer(learning_rate=learning_rate, initial_accumulator_value=1e-8)elif FLAGS.optimizer == 'Momentum':optimizer = tf.train.MomentumOptimizer(learning_rate=learning_rate, momentum=0.95)elif FLAGS.optimizer == 'ftrl':optimizer = tf.train.FtrlOptimizer(learning_rate)train_op = optimizer.minimize(loss, global_step=tf.train.get_global_step())# Provide an estimator spec for `ModeKeys.TRAIN` modesif mode == tf.estimator.ModeKeys.TRAIN:return tf.estimator.EstimatorSpec(mode=mode,predictions=predictions,loss=loss,train_op=train_op)- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
10.AFM: 對簡化版NFM進(jìn)行加權(quán)求和
NFM的主要創(chuàng)新點(diǎn)是在FM過程中添加了逐元素相乘的運(yùn)算來增加模型的復(fù)雜度。但沒有在此基礎(chǔ)上添加更復(fù)雜的運(yùn)算過程,比如對加權(quán)求和。Jun Xiao等在2017年提出了注意力因子分解模型(Attentional Factorization Machine,AFM)就是在這個方向上的改進(jìn)。其計(jì)算圖如下所示:?
?
AFM的特點(diǎn)是:
11.PNN:通過改進(jìn)向量乘法運(yùn)算延遲FM的實(shí)現(xiàn)過程
再回到FM。既然AFM、NFM可以通過添加逐元素乘法的運(yùn)算來增加模型的復(fù)雜度,那向量乘法有這么多,可否用其他的方法增加FM復(fù)雜度?答案是可以的。Huifeng Guo等在2016年提出了基于向量積的神經(jīng)網(wǎng)絡(luò)(Product-based Neural Networks,PNN)就是一個典型例子。其簡化計(jì)算圖如下所示:?
對比之前模型的計(jì)算圖,我們可以發(fā)現(xiàn)PNN的基本特點(diǎn)是:
?
此處分別附上PNN的內(nèi)積與外積形式代碼,完整數(shù)據(jù)和代碼請戳這里。
class PNN1(Model):def __init__(self, field_sizes=None, embed_size=10, layer_sizes=None, layer_acts=None, drop_out=None,embed_l2=None, layer_l2=None, init_path=None, opt_algo='gd', learning_rate=1e-2, random_seed=None):Model.__init__(self)init_vars = []num_inputs = len(field_sizes)for i in range(num_inputs):init_vars.append(('embed_%d' % i, [field_sizes[i], embed_size], 'xavier', dtype))num_pairs = int(num_inputs * (num_inputs - 1) / 2)node_in = num_inputs * embed_size + num_pairs# node_in = num_inputs * (embed_size + num_inputs)for i in range(len(layer_sizes)):init_vars.append(('w%d' % i, [node_in, layer_sizes[i]], 'xavier', dtype))init_vars.append(('b%d' % i, [layer_sizes[i]], 'zero', dtype))node_in = layer_sizes[i]self.graph = tf.Graph()with self.graph.as_default():if random_seed is not None:tf.set_random_seed(random_seed)self.X = [tf.sparse_placeholder(dtype) for i in range(num_inputs)]self.y = tf.placeholder(dtype)self.keep_prob_train = 1 - np.array(drop_out)self.keep_prob_test = np.ones_like(drop_out)self.layer_keeps = tf.placeholder(dtype)self.vars = init_var_map(init_vars, init_path)w0 = [self.vars['embed_%d' % i] for i in range(num_inputs)]xw = tf.concat([tf.sparse_tensor_dense_matmul(self.X[i], w0[i]) for i in range(num_inputs)], 1)xw3d = tf.reshape(xw, [-1, num_inputs, embed_size])row = []col = []for i in range(num_inputs-1):for j in range(i+1, num_inputs):row.append(i)col.append(j)# batch * pair * kp = tf.transpose(# pair * batch * ktf.gather(# num * batch * ktf.transpose(xw3d, [1, 0, 2]),row),[1, 0, 2])# batch * pair * kq = tf.transpose(tf.gather(tf.transpose(xw3d, [1, 0, 2]),col),[1, 0, 2])p = tf.reshape(p, [-1, num_pairs, embed_size])q = tf.reshape(q, [-1, num_pairs, embed_size])ip = tf.reshape(tf.reduce_sum(p * q, [-1]), [-1, num_pairs])# simple but redundant# batch * n * 1 * k, batch * 1 * n * k# ip = tf.reshape(# tf.reduce_sum(# tf.expand_dims(xw3d, 2) *# tf.expand_dims(xw3d, 1),# 3),# [-1, num_inputs**2])l = tf.concat([xw, ip], 1)for i in range(len(layer_sizes)):wi = self.vars['w%d' % i]bi = self.vars['b%d' % i]l = tf.nn.dropout(activate(tf.matmul(l, wi) + bi,layer_acts[i]),self.layer_keeps[i])l = tf.squeeze(l)self.y_prob = tf.sigmoid(l)self.loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=l, labels=self.y))if layer_l2 is not None:self.loss += embed_l2 * tf.nn.l2_loss(xw)for i in range(len(layer_sizes)):wi = self.vars['w%d' % i]self.loss += layer_l2[i] * tf.nn.l2_loss(wi)self.optimizer = get_optimizer(opt_algo, learning_rate, self.loss)config = tf.ConfigProto()config.gpu_options.allow_growth = Trueself.sess = tf.Session(config=config)tf.global_variables_initializer().run(session=self.sess)- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
12.DCN:高階FM的降維實(shí)現(xiàn)
以上的FM推廣形式,主要是對FM進(jìn)行二階特征組合。高階特征組合是通過MLP實(shí)現(xiàn)的。但這兩種實(shí)現(xiàn)方式是有很大不同的,FM更多是通過向量embedding之間的內(nèi)積來實(shí)現(xiàn),而MLP則是在向量embedding之后一層一層進(jìn)行權(quán)重矩陣乘法實(shí)現(xiàn)。可否直接將FM的過程在高階特征組合上進(jìn)行推廣?答案是可以的。Ruoxi Wang等在2017提出的深度與交叉神經(jīng)網(wǎng)絡(luò)(Deep & Cross Network,DCN)就是在這個方向進(jìn)行改進(jìn)的。DCN的計(jì)算圖如下:?
DCN的特點(diǎn)如下:
?
13.Wide&Deep: DeepFM與DCN的基礎(chǔ)框架
開篇已經(jīng)提到,本文思路有兩條主線。到此為止已經(jīng)將基于FM的主線介紹基本完畢。接下來將串講從embedding+MLP自身的演進(jìn)特點(diǎn)的CTR預(yù)估模型主線,而這條思路與我們之前的FM思路同樣有千絲萬縷的聯(lián)系。?
Google在2016年提出的寬度與深度模型(Wide&Deep)在深度學(xué)習(xí)CTR預(yù)估模型中占有非常重要的位置,它奠定了之后基于深度學(xué)習(xí)的廣告點(diǎn)擊率預(yù)估模型的框架。?
Wide&Deep將深度模型與線性模型進(jìn)行聯(lián)合訓(xùn)練,二者的結(jié)果求和輸出為最終點(diǎn)擊率。其計(jì)算圖如下:?
我們將Wide&Deep的計(jì)算圖與之前的模型進(jìn)行對比可知:
?
此處附上DeepFM的代碼實(shí)現(xiàn),完整數(shù)據(jù)和代碼請戳這里:
def get_model(model_type, model_dir):print("Model directory = %s" % model_dir)# 對checkpoint去做設(shè)定runconfig = tf.contrib.learn.RunConfig(save_checkpoints_secs=None,save_checkpoints_steps = 100,)m = None# 寬模型if model_type == 'WIDE':m = tf.contrib.learn.LinearClassifier(model_dir=model_dir,feature_columns=wide_columns)# 深度模型if model_type == 'DEEP':m = tf.contrib.learn.DNNClassifier(model_dir=model_dir,feature_columns=deep_columns,hidden_units=[100, 50, 25])# 寬度深度模型if model_type == 'WIDE_AND_DEEP':m = tf.contrib.learn.DNNLinearCombinedClassifier(model_dir=model_dir,linear_feature_columns=wide_columns,dnn_feature_columns=deep_columns,dnn_hidden_units=[100, 70, 50, 25],config=runconfig)print('estimator built')return m- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
14.Deep Cross: DCN由其殘差網(wǎng)絡(luò)思想進(jìn)化
由K. He等提出的深度殘差網(wǎng)絡(luò)能夠大大加深神經(jīng)網(wǎng)絡(luò)的深度,同時(shí)不會引起退化的問題,顯著提高了模型的精度。Ying Shan等將該思路應(yīng)用到廣告點(diǎn)擊率預(yù)估模型中,提出深度交叉模型(DeepCross,2016DeepCross,2016)。Deep Cross的計(jì)算圖如下:?
將Deep Cross與之前的模型對比,可以發(fā)現(xiàn)其特點(diǎn)是:
?
15.DIN:對同領(lǐng)域歷史信息引入注意力機(jī)制的MLP
以上神經(jīng)網(wǎng)絡(luò)對同領(lǐng)域離散特征的處理基本是將其嵌入后直接求和,這在一般情況下沒太大問題。但其實(shí)可以做得更加精細(xì)。比如對于歷史統(tǒng)計(jì)類特征。以用戶歷史瀏覽的商戶id為例,假設(shè)用戶歷史瀏覽了10個商戶,這些商戶id的常規(guī)處理方法是作為同一個領(lǐng)域的特征嵌入后直接求和得到一個嵌入向量。但這10個商戶只有一兩個商戶與當(dāng)前被預(yù)測的廣告所在的商戶相似,其他商戶關(guān)系不大。增加這兩個商戶在求和過程中的權(quán)重,應(yīng)該能夠更好地提高模型的表現(xiàn)力。而增加求和權(quán)重的思路就是典型的注意力機(jī)制思路。?
由?Bahdanau et al. (2015)?引入的現(xiàn)代注意力機(jī)制,本質(zhì)上是加權(quán)平均(權(quán)重是模型根據(jù)數(shù)據(jù)學(xué)習(xí)出來的),其在機(jī)器翻譯上應(yīng)用得非常成功。受注意力機(jī)制的啟發(fā),Guorui Zhou等在2017年提出了深度興趣網(wǎng)絡(luò)(Deep Interest Network,DIN)。DIN主要關(guān)注用戶在同一領(lǐng)域的歷史行為特征,如瀏覽了多個商家、多個商品等。DIN可以對這些特征分配不同的權(quán)重進(jìn)行求和。其網(wǎng)絡(luò)結(jié)構(gòu)圖如下:?
?
16.多任務(wù)視角:信息的遷移與補(bǔ)充
對于數(shù)據(jù)驅(qū)動的解決方案而言,數(shù)據(jù)和模型同樣重要,數(shù)據(jù)(特征)通常決定了效果的上限,各式各樣的模型會以不同的方式去逼近這個上限。而所有算法應(yīng)用的老司機(jī)都知道很多場景下,如果有更多的數(shù)據(jù)進(jìn)行模型訓(xùn)練,效果一般都能顯著得到提高。廣告也是一樣的場景,在很多電商的平臺上會有很多不同場景的廣告位,每個場景蘊(yùn)含了用戶的不同興趣的表達(dá),這些信息的匯總與融合可以帶來最后效果的提升。但是將不同場景的數(shù)據(jù)直接進(jìn)行合并用來訓(xùn)練(ctr/cvr)模型,結(jié)果很多時(shí)候并不是很樂觀,仔細(xì)想想也是合理的,不同場景下的樣本分布存在差異,直接對樣本累加會影響分布導(dǎo)致效果負(fù)向。?
而深度學(xué)習(xí)發(fā)展,使得信息的融合與應(yīng)用有了更好的進(jìn)展,用Multi?taskMulti?task?learning(MTL)learning(MTL)的方式可以很漂亮的解決上面提到的問題。我們不直接對樣本進(jìn)行累加和訓(xùn)練,而是像上圖所示,把兩個場景分為兩個task,即分為兩個子網(wǎng)絡(luò)。對單個網(wǎng)絡(luò)而言,底層的embedding層的表達(dá)受限于單場景的數(shù)據(jù)量,很可能學(xué)習(xí)不充分。而上圖這樣的網(wǎng)絡(luò)結(jié)合,使得整個訓(xùn)練過程有了表示學(xué)習(xí)的共享(Shared Lookup Table),這種共享有助于大樣本的子任務(wù)幫助小樣本的子任務(wù),使得底層的表達(dá)學(xué)習(xí)更加充分。?DeepFM和DCN也用到了這個思路!只是它們是對同一任務(wù)的不同模型進(jìn)行結(jié)合,而多任務(wù)學(xué)習(xí)是對不同任務(wù)的不同模型進(jìn)行結(jié)合。而且,我們可以玩得更加復(fù)雜。?
Multi-task learning(MTL)整個結(jié)構(gòu)的上層的不同的task的子網(wǎng)絡(luò)是不一樣的,這樣每個子網(wǎng)絡(luò)可以各自去擬合自己task對應(yīng)的概念分布。并且,取決于問題與場景的相似性和復(fù)雜度,可以把底層的表達(dá)學(xué)習(xí),從簡單的共享embedding到共享一些層次的表達(dá)。極端的情況是我們可以直接共享所有的表達(dá)學(xué)習(xí)(representation learning)部分,而只接不同的網(wǎng)絡(luò)head來完成不一樣的任務(wù)。這樣帶來的另外一個好處是,不同的task可以共享一部分計(jì)算,從而實(shí)現(xiàn)計(jì)算的加速。?
值得一提的另一篇paper是阿里媽媽團(tuán)隊(duì)提出的“完整空間多任務(wù)模型”(Entire Space Multi-Task Model,ESMM),也是很典型的多任務(wù)學(xué)習(xí)和信息補(bǔ)充思路,這篇paper解決的問題不是ctr(點(diǎn)擊率)預(yù)估而是cvr(轉(zhuǎn)化率)預(yù)估,傳統(tǒng)CVR預(yù)估模型會有比較明顯的樣本選擇偏差(sample selection bias)和訓(xùn)練數(shù)據(jù)過于稀疏(data sparsity )的問題,而ESMM模型利用用戶行為序列數(shù)據(jù),在完整的樣本數(shù)據(jù)空間同時(shí)學(xué)習(xí)點(diǎn)擊率和轉(zhuǎn)化率(post-view clickthrough&conversion rate,CTCVR),在一定程度上解決了這個問題。?
在電商的場景下,用戶的決策過程很可能是這樣的,在觀察到系統(tǒng)展現(xiàn)的推薦商品列表后,點(diǎn)擊自己感興趣的商品,進(jìn)而產(chǎn)生購買行為。所以用戶行為遵循這樣一個決策順序:impression → click → conversion。CVR模型旨在預(yù)估用戶在觀察到曝光商品進(jìn)而點(diǎn)擊到商品詳情頁之后購買此商品的概率,即pCVR = p(conversion|click,impression)。?
預(yù)估點(diǎn)擊率pCTR,預(yù)估點(diǎn)擊下單率pCVR和預(yù)估點(diǎn)擊與下單率pCTCVR關(guān)系如下。?
?
傳統(tǒng)的CVR預(yù)估任務(wù)通常采用類似于CTR預(yù)估的技術(shù)進(jìn)行建模。但是不同于CTR預(yù)估任務(wù)的是,這個場景面臨一些特有的挑戰(zhàn):1) 樣本選擇偏差;2) 訓(xùn)練數(shù)據(jù)稀疏;3) 延遲反饋等。?
ESMM模型提出了下述的網(wǎng)絡(luò)結(jié)構(gòu)進(jìn)行問題建模?
EMMS的特點(diǎn)是:
?
注意到pCTR 和pCTCVR是在整個樣本空間上建模得到的,pCVR 只是一個中間變量。因此,ESMM模型是在整個樣本空間建模,而不像傳統(tǒng)CVR預(yù)估模型那樣只在點(diǎn)擊樣本空間建模。
17.各種模型的對比和總結(jié)
前面介紹了各種基于深度學(xué)習(xí)的廣告點(diǎn)擊率預(yù)估算法模型,針對不同的問題、基于不同的思路,不同的模型有各自的特點(diǎn)。各個模型具體關(guān)系比較如下表1所示:?
表 1. 各模型對比
?
| Wide&Deep | √ | × | × |
| FNN | √ | √ | × |
| DeepFM | √ | √ | × |
| NFM | √ | √ | × |
| DIN | √ | × | √ |
| AFM | × | √ | √ |
| Deep Cross | √ | × | × |
| DCN | √ | √ | × |
本文從開篇就說明這些模型推演的核心思路是“通過設(shè)計(jì)網(wǎng)絡(luò)結(jié)構(gòu)進(jìn)行組合特征的挖掘”,其在各個模型的實(shí)現(xiàn)方式如下:
當(dāng)然,廣告點(diǎn)擊率預(yù)估深度學(xué)習(xí)模型還有很多,比如Jie Zhu提出的基于決策樹的神經(jīng)網(wǎng)絡(luò)(Deep Embedding Forest)將深度學(xué)習(xí)與樹型模型結(jié)合起來。如果數(shù)據(jù)特征存在圖像或者大量文本相關(guān)特征,傳統(tǒng)的卷積神經(jīng)網(wǎng)絡(luò)、循環(huán)神經(jīng)網(wǎng)絡(luò)均可以結(jié)合到廣告點(diǎn)擊率預(yù)估的場景中。各個深度模型都有相應(yīng)的特點(diǎn),限于篇幅,我們就不再贅述了。
18.后記
目前深度學(xué)習(xí)的算法層出不窮,看論文確實(shí)有些應(yīng)接不暇。我們的經(jīng)驗(yàn)有兩點(diǎn):要有充分的生產(chǎn)實(shí)踐經(jīng)驗(yàn),同時(shí)要有扎實(shí)的算法理論基礎(chǔ)。很多論文的亮點(diǎn)其實(shí)是來自于實(shí)際做工程的經(jīng)驗(yàn)。也辛虧筆者一直都在生產(chǎn)一線并帶領(lǐng)算法團(tuán)隊(duì)進(jìn)行工程研發(fā)(當(dāng)然也因此荒廢了近2年的博客,T△T ),積淀了一些特征工程、模型訓(xùn)練的經(jīng)驗(yàn),才勉強(qiáng)跟得上新論文。比如DIN“對用戶側(cè)的某領(lǐng)域歷史特征基于廣告?zhèn)鹊耐I(lǐng)域特征進(jìn)行加權(quán)求和”的思想,其實(shí)與傳統(tǒng)機(jī)器學(xué)習(xí)對強(qiáng)業(yè)務(wù)相關(guān)特征進(jìn)行針對性特征組合的特征工程思路比較相似。另一方面,對深度學(xué)習(xí)的經(jīng)典、前沿方法的熟悉也很重要。從前面我們的串講也能夠看出,CTR預(yù)估作為一個業(yè)務(wù)特點(diǎn)很強(qiáng)的場景,在應(yīng)用深度學(xué)習(xí)的道路上,也充分借鑒了注意力機(jī)制、殘差網(wǎng)絡(luò)、聯(lián)合訓(xùn)練、多任務(wù)學(xué)習(xí)等經(jīng)典的深度學(xué)習(xí)方法。了解博主的朋友也知道我們一直推崇理論與實(shí)踐相結(jié)合的思路,我們自身對這條經(jīng)驗(yàn)也非常受用。當(dāng)然,計(jì)算廣告是一個很深的領(lǐng)域,自己研究尚淺,串講難免存在紕漏。歡迎大家指出問題,共同交流學(xué)習(xí)。
參考文獻(xiàn)
J. Xiao, H. Ye, X. He, H. Zhang, F. Wu, and T.-S. Chua. Attentional factorization machines: Learning the weight of feature interactions via attention networks. In IJCAI, 2017.
Ying Shan, T Ryan Hoens, Jian Jiao, Haijing Wang, Dong Yu, and JC Mao. 2016. Deep Crossing: Web-Scale Modeling without Manually Cra ed Combinatorial Features. In Proceedings of the 22nd ACM SIGKDD International Conference on Knowledge Discovery and Data Mining. ACM, 255–262.
總結(jié)
以上是生活随笔為你收集整理的从FM推演各深度学习CTR预估模型的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spark RDD使用详解5--Acti
- 下一篇: 【从传统方法到深度学习】图像分类