推薦系統:CTR模型學習總結--LR、FM、FFM、Wide and Deep、DeepFM

推薦系統方法綜述

推薦系統的目的

評價指標

Accuracy

accuracy=accuracy=\frac{預測正確的樣本數}{總樣本數}
缺點 對樣本分佈敏感。在測試集裏,有100個sample,99個反例,只有1個正例。如果我的模型不分青紅皁白對任意一個sample都預測是反例,那麼我的模型的accuracy是 正確的個數/總個數 = 99/100 = 99%

logloss

2分類問題:
logloss=i=1nyilog(p(xi))+(1yi)log(1p(xi))logloss=-\sum_{i=1}^ny_ilog(p(x_i))+(1-y_i)log(1-p(x_i))

AUC

爲什麼 AUC 和 logloss 比 accuracy 更常用呢?因爲很多機器學習的模型對分類問題的預測結果都是概率,如果要計算 accuracy,需要先把概率轉化成類別,這就需要手動設置一個閾值,如果對一個樣本的預測概率高於這個預測,就把這個樣本放進一個類別裏面,低於這個閾值,放進另一個類別裏面。
所以這個閾值很大程度上影響了 accuracy 的計算。使用 AUC 或者 logloss 可以避免把預測概率轉換成類別1

AUC 是 Area under curve 的首字母縮寫。AUC 就是 ROC 曲線下的面積,衡量學習器優劣的一種性能指標。
在這裏插入圖片描述

ROC 曲線的橫座標是假正率,縱座標是真正率,由上圖:
TPR=TPTP+FN=P(TPY=1)TPR=\frac{TP}{TP+FN}=P(TP|Y=1)
FPR=FPFP+TN=P(FPY=1)FPR=\frac{FP}{FP+TN}=P(FP|Y=1)
其縱軸、橫軸均是條件於真實 label Y的概率的,我們講這個叫條件概率。那麼意思就是說,無論Y的真實概率是多少,都不會影響 TPR 和 FPR。也就是說,這兩個 metric 是不會受 imbalanced data 影響的,相反precision=TPTP+FP=P(TPY^=1)precision=\frac{TP}{TP+FP=P(TP|\hat{Y}=1)},是基於預測label Y 的概率的,會隨着測試集裏面的正反比例而變化。

AUC代表模型預估樣本之間的排序關係,即正負樣本之間預測的 gap 越大,AUC越大。另外值得注意的是,AUC的計算方法同時考慮了學習器對於正例和負例的分類能力,在樣本不平衡的情況下,依然能夠對分類器做出合理的評價。AUC對樣本類別是否均衡並不敏感,這也是不均衡樣本通常用AUC評價學習器性能的一個原因。
ROC 曲線越靠近左上角,模型的查全率就越高。最靠近左上角的ROC曲線上的點是分類錯誤最少的最好閾值,其假正例和假反例總數最少。可以對不同的學習器比較性能。將各個學習器的ROC曲線繪製到同一座標中,直觀地鑑別優劣,靠近左上角的ROC曲所代表的學習器準確性最高。
ROC曲線能很容易的查出任意閾值對學習器的泛化性能影響。
有助於選擇最佳的閾值。ROC曲線越靠近左上角,模型的查全率就越高。最靠近左上角的ROC曲線上的點是分類錯誤最少的最好閾值,其假正例和假反例總數最少。
可以對不同的學習器比較性能。將各個學習器的ROC曲線繪製到同一座標中,直觀地鑑別優劣,靠近左上角的ROC曲所代表的學習器準確性最高。

F1 score

F1=2precisionrecallprecision+recallF1=\frac{2*precision*recall}{precision+recall}
基本上問題就是如果你的兩個模型,一個precision特別高,recall特別低,另一個recall特別高,precision特別低的時候,f1-score可能是差不多的,你也不能基於此來作出選擇。

Collaborative Fliter

CTR

根據用戶的詢問,給於用戶信息度相關的廣告,會極大的增加用戶點擊廣告的可能性,會增加用戶的體驗,同時也會增加廣告公司的收入。

CTR 指的是click through rate,代表點擊該廣告的概率。例如某個廣告ad在過去一段時間內被展示了1000次,並且受到50次的點擊次數,那麼 CTR (ad)=0.05.

因此,如何根據歷史廣告信息和用戶信息,較爲精準的預測新的廣告會被點擊的概率具有重要意義,
但是這裏存在一個問題:這種根據過去的信息預測點擊率的方法有着極大的 Variance ,並且根據 大數定理 廣告需要被大量投遞後,才能獲得較爲精準的 CTR 結果。

因此如何能夠精準構建廣告信息和 CTR 之間的數學模型有着極其重要的意義。
CTR=f(Xad) CTR=f(X_{ad})
進一步思考,我們可以發現廣告點擊問題本質上是一個2分類問題: {點擊,不點擊} ,因此可以構建基於機器學習方法的 CTR 估計模型。

大數定理 :大數定律說如果統計數據足夠大,那麼事物出現的頻率就能無限接近他的期望值。
大數定理有3種:辛欽大數定理、切比雪夫、伯努利大數定理

辛欽大數定理:

推導公式
在這裏插入圖片描述
切比雪夫不等式:在不知道隨機變量的具體概率密度函數的情況下,可以根據總體的均值和方差時,用切比雪夫不等式來估算一定條件下的概率。
在這裏插入圖片描述
伯努利大數定理:
在這裏插入圖片描述
伯努利大數定理是日常中最常被使用的,它的直觀表達就是隻要做的試驗夠多,出現的次數除以總次數的結果接近統計概率p,這也是頻率到概率概念演變的理論基礎舉個例子,結合概率論裏面的概念。”拋5次硬幣“是一種試驗,一共作n重,“ 3次出現正面”,稱爲事件A,n重試驗出現A的次數爲fA另外已知"拋5次3次正面"的概率是p,這是一個先驗可統計概率。如果n很大,則出現A的次數除以n就可做爲統計的概率p.
引用博文大數定理的通俗理解2

中心極限定理
(1). 樣本的平均值約等於總體的平均值
(2).不管樣本是什麼分佈,任意一個總體的樣本平均值會圍繞着總體平均值周圍,並且呈正態分佈。
中心極限定理3
基於機器學習LR的CTR估計的發展歷程如下圖:
在這裏插入圖片描述

LR

根據前文所訴:CTR 估計問題本質上是一個2分類問題,不妨設{點擊,不點擊}={1,0},LR模型使用Logit將廣告的特徵值加權和映射到(0,1)區間,映射後的值就是 CTR 估計值.
y^=w0+i=1nwixi \hat{y}=w_0+\sum_{i=1}^n w_ix_i
將輸入 y^\hat{y}通過logit函數之後獲得ctr值。
CTR=logit(y^) CTR=logit(\hat{y})
優化目標
首先可求當根據原始特徵XX時,預測爲1的概率:
P(y=1x;w)=11+ewTX P(y=1|x;w)= \frac{1}{1+e^{-w^TX}}
因此預測爲0的概率:
P(y=0x;w)=111+ewTX=11+ewTX P(y=0|x;w)=1- \frac{1}{1+e^-w^TX}=\frac{1}{1+e^{w^TX}}
根據極大似然思想:
J(w)=maxwi=1nP(y=1x;w)y=1(y=0x;w)y=0=maxi=1nP(y=1x;w)y(y=0x;w)1yJ(w)=mini=1myilog(logit(Xi))+(1yi)log(1logit(Xi)) J(w)=max_{w} \prod_{i=1}^{n} P(y=1|x;w)^{y=1}(y=0|x;w)^{y=0}\\=max \prod_{i=1}^{n} P(y=1|x;w)^{y}(y=0|x;w)^{1-y}\\J(w)=min -\sum_{i=1}^m y^ilog(logit(X^i))+(1-y^i)log(1-logit(X^i))

當label={-1,1}時:J(w)=mini=1mlog(1+eyy^)J(w)=min\sum_{i=1}^m log(1+e^{-y\hat{y}})
當label是多目標時,有TT個類別:J(w)=mini=1mj=1Tyijlog(pij)J(w)=min-\sum_{i=1}^m\sum_{j=1}^T y_{ij}log(p_{ij})

LR方法存在很明顯的缺陷: (1).LR方法無法捕捉特徵之間的聯繫。(2).LR方法在數據特徵有缺失時或者特徵空間很大時表現不佳。

POLY2

由於LR方法無法捕捉特徵之間的聯繫,因此可以組合特徵建立特徵之間的關係:
y^=w0+i=1nwixi+i=1nj=i+1nwijxixj\hat{y}=w_0+\sum_{i=1}^n w_ix_i+\sum_{i=1}^n\sum_{j=i+1}^n w_{ij}x_ix_j
由於對特徵進行了組合,因此數據矩陣必定是一個非常稀疏的矩陣。由於y^wij=xixj,wijxi=0xj=0調\frac{\partial\hat{y}}{\partial w_{ij}}=x_ix_j,因此w_{ij}在x_i=0或者x_j=0時不能得到很好的調整。
POLY2 方法雖然能夠捕捉特徵之間的聯繫(度爲2),但是由於數據稀疏問題,往往不能得到較好的結果。

FM

FM 方法不同於 PLOY2 ,FM 方法通過對每一位特徵引入一個隱向量VV,使得 FM 方法能夠使用稀疏的數據矩陣中,獲得可信的結果。
y^=w0+i=1nwixi+i=1nj=i+1n<Vi,Vj>xixj<Vi,Vj>=f=1kVifVjf\hat{y}=w_0+\sum_{i=1}^n w_ix_i+\sum_{i=1}^n\sum_{j=i+1}^n <V_i,V_j>x_ix_j\\<V_i,V_j>=\sum_{f=1}^kV_{if}*V_{jf}

FM 方法將特徵的聯繫矩陣WW分解爲W=VVTW=V\cdot V^T。其理論依據是,任何的正定矩陣WW總存在VnkV_{n*k},使得W=VVTW=V\cdot V^T,在kk充分大的條件下。

FM 方法通過提出隱向量的思想解決了 PLOY2 方法中wijw_{ij}xi=0x_i=0或者xj=0x_j=0時不能得到很好的調整的問題。FM 通過分解WW打破了矩陣裏面參數的獨立性,即wij=<Vi,Vj>w_{ij}=<V_i,V_j>ViV_iVjV_j 可以由其他特徵下學習的結果,因此 FM 能夠在稀疏數據集上獲得較好的結果。
FM 方法在不調整計算的情況下的時間複雜度O(kn2)O(kn^2),但是通過調整上式,其時間複雜度可降爲O(kn)O(kn)
i=1nj=i+1n<Vi,Vj>xixj=12i=1nj=1n<Vi,Vj>xixj12i=1n<Vi,Vi>xixi=12(i=1nj=1ni=fkvifvjfxixji=1nf=1kvifvifxixi)=12f=1k((i=1nvifxi)2i=1nvif2xi2)\sum_{i=1}^n\sum_{j=i+1}^n <V_i,V_j>x_ix_j\\= \frac{1}{2}\sum_{i=1}^n\sum_{j=1}^n <V_i,V_j>x_ix_j-\frac{1}{2}\sum_{i=1}^n <V_i,V_i>x_ix_i \\= \frac{1}{2}(\sum_{i=1}^n\sum_{j=1}^n \sum_{i=f}^kv_{if}v_{jf}x_ix_j-\sum_{i=1}^n\sum_{f=1}^kv_{if}v_{if}x_ix_i)\\=\frac{1}{2}\sum_{f=1}^k((\sum_{i=1}^nv_{if}x_i)^2-\sum_{i=1}^nv_{if}^2x_i^2)
很明顯的可以看出(i=1nvifxi)2(\sum_{i=1}^nv_{if}x_i)^2這一項不需要重複計算,因此時間複雜度降爲O(kn)O(kn)


求解VV 通過SGD方法求解VV:
θy^(X)=1, when θ=w0θy^(X)=xi, when θ=wiθy^(X)=xij=1nvjfxjvifxi2, when θ=vif \frac{\partial}{\partial\theta}\hat{y}(X)=1, \ when\ \theta=w_0\\ \frac{\partial}{\partial\theta}\hat{y}(X)=x_i, \ when\ \theta=w_i\\ \frac{\partial}{\partial\theta}\hat{y}(X)=x_i\sum_{j=1}^nv_{jf}x_j-v_{if}x_i^2, \ when\ \theta=v_{if}
FM 的優點:
(1). 可以在稀疏的數據矩陣中對特徵進行交叉。
(2).預測和參數的調整都是基於O(kn)O(kn)的時間複雜度。

#########2020.5.1 hwf#############
import tensorflow as tf
import os
import numpy as np
class Fm():
    def __init__(self,num_classes,k,batch_size,feature_size,train_feature,train_label,save_dir):
        self.num_classes=num_classes
        self.k=k
        self.batch_size=batch_size
        self.feature_size=feature_size
        self.train_feature=train_feature
        self.train_label=train_label
        self.save_dir=save_dir
        
    def bulid_model(self):
        self.x=tf.placeholder('float32',[None,self.feature_size])
        self.y=tf.placeholder('float32',[None,self.num_classes])
        with tf.variable_scope('linear_layer'):
            w0=tf.get_variable('w0',shape=[self.num_classes],initializer=tf.zeros_initializer)
            w=tf.get_variable('w',shape=[self.feature_size,self.num_classes],initializer=tf.truncated_normal_initializer(mean=0.0,stddev=0.01))
            linear_out=tf.add(tf.matmul(self.x,w),w0)
        with tf.variable_scope('interaction_layer'):
            embeding=tf.get_variable('v',shape=[self.feature_size,self.k],initializer=tf.truncated_normal_initializer(mean=0.0,stddev=0.01))
            interaction_out=tf.multiply(0.5
                                        ,tf.reduce_sum(
                                           tf.subtract(
                                               tf.pow(tf.matmul(self.x,embeding),2),
                                               tf.matmul(tf.pow(self.x,2),tf.pow(embeding,2))),axis=1,keepdims=True))
        with tf.variable_scope('out_layer'):
            output=tf.add(linear_out,interaction_out)

        with tf.variable_scope('accuracy'):
            correct_prediction = tf.equal(tf.cast(tf.argmax(output,1), tf.float32), tf.cast(tf.argmax(self.y,1), tf.float32))
            self.accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
            # add summary to accuracy
            tf.summary.scalar('accuracy', self.accuracy)
        if self.num_classes==2:
            y_prob=tf.nn.sigmoid(output)
            cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(labels=self.y, logits=y_prob)
        elif self.num_classes>2:
            y_prob=tf.nn.softmax(output)
            cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=self.y, logits=y_prob)
        mean_loss = tf.reduce_mean(cross_entropy)
        self.loss = mean_loss
        tf.summary.scalar('loss', self.loss)
        self.optimizer=tf.train.AdamOptimizer()
        self.train_op=self.optimizer.minimize(self.loss)

    # def next_batch(self):
    #     print('mext+_natch',self.train_feature)
    #     print(self.batch_size)
    #     input_queue = tf.train.slice_input_producer([self.train_feature,self.train_label], shuffle=False)
    #     feature_batch, label_batch = tf.train.batch(input_queue, batch_size=self.batch_size, num_threads=2, capacity=128,allow_smaller_final_batch=True)
    #     return feature_batch,label_batch

    def shuffle_list(self,data):
        num = data[0].shape[0]
        p = np.random.permutation(num)
        return [d[p] for d in data]

    def batch_generator(self,data, batch_size, shuffle=False):
        if shuffle:
            data = self.shuffle_list(data)

        batch_count = 0
        while True:
            if batch_count * batch_size + batch_size > len(data[0]):
                batch_count = 0

                if shuffle:
                    data = self.shuffle_list(data)

            start = batch_count * batch_size
            end = start + batch_size
            batch_count += 1
            yield [d[start:end] for d in data]

    def train(self,iteration):
        self.Saver = tf.train.Saver(max_to_keep=100)
        merge = tf.summary.merge_all()
        nums_batch=len(self.train_label)//self.batch_size+1
        init = tf.initialize_all_variables()
        ckpt=tf.train.get_checkpoint_state(self.save_dir)
        with tf.Session() as self.sess:
            coord = tf.train.Coordinator()
            threads = tf.train.start_queue_runners(self.sess, coord)
            self.sess.run(init)
            train_writer=tf.summary.FileWriter('./log/train_logs',self.sess.graph)
            if ckpt and ckpt.model_model_checkpoint_path:
                self.Saver.restore(self.sess,ckpt.model_model_checkpoint_path)
                print("加載模型成功"+ckpt.model_model_checkpoint_path)
                global_step=int(ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1])
            else:
                global_step =0
            for epoch in range(iteration):
                for _ in range(nums_batch):
                    #feature_batch, label_batch = self.next_batch()
                    feature, label = next(self.batch_generator([self.train_feature,self.train_label],batch_size=self.batch_size))
                    #feature,label = self.sess.run([feature_batch, label_batch])
                    feed_dic={self.x:feature, self.y:label}
                    loss, accuracy, summary, _ = self.sess.run([self.loss, self.accuracy,merge, self.train_op], feed_dict=feed_dic)
                    train_writer.add_summary(summary, global_step=global_step)
                    global_step+=1
                if epoch % 100 == 0:
                    print('save_model_{}'.format(epoch))
                    self.Saver.save(self.sess, os.path.join(self.save_dir, 'fm'), global_step=global_step)
                print("Epoch {1},  loss = {0:.3g}, accuracy={2:.3g}".format(loss, epoch + 1,accuracy))
            coord.request_stop()
            coord.join(threads)
if __name__=='__main__':
    x_train,  x_test,y_train, y_test = load_data()
    print(x_train)
    # initialize the model
    num_classes = 5
    lr = 0.01
    batch_size = 128
    k = 40
    #reg_l1 = 2e-2
    #reg_l2 = 0
    feature_length = x_train.shape[1]
    save_dir=r'./model/'
    # initialize FM model
    model =Fm(num_classes,k,batch_size,feature_length,x_train,y_train,save_dir=save_dir)
    model.bulid_model()
    model.train(12222)
    # build graph for model

FFM

FM 方法有仍然存在問題, 離散特徵xix_i進行onehot編碼後,xix_i會被編碼成xi1,xi2,xi3..xitx_{i1},x_{i2},x_{i3}..x_{it},這些feature被稱爲一個field。其中特徵xfiledi,featurejx_{filed_i,feature_j}的隱向量VijV_{ij}跟其他的filed的特徵進行組合時,如 <VijVab><V_{ij}\cdot V_{ab}><VijVcd><V_{ij}\cdot V_{cd}>由於 xfiledi,featurej,xfileda,featurebx_{filed_i,feature_j},x_{filed_a,feature_b}xfiledi,featurej,xfiledc,featuredx_{filed_i,feature_j},x_{filed_c,feature_d}屬於不同field,因此他們的隱向量組合也會不同。
FFM 對的每一個feature都會包含ff個隱向量,ff是其他field的個數。
yffm=i=1nj=i+1n<Vi,f1,Vj,f2>xixjf1jfiledf2ifield y_{ffm}=\sum_{i=1}^n\sum_{j=i+1}^n <V_{i,f1},V_{j,f2}>x_ix_j\\其中f1表示j的filed,f2表示i的field。
ffm的變量個數爲nfknfk,時間複雜度爲O(nnk)O(nnk)

小結

Model variable complexity
LM nn O(n)O(n)
Poly2 BB O(n2)O(n^2)
FM nknk O(nk)O(nk)
FFm nfknfk O(nfk)O(nfk)

LR+GBDT

核心思想:模型想要得到好的預測結果的最重要的影響因素是 好的特徵組合,即特徵決定下限,模型決定上限。因此利用GBDT算法對原始特徵進行特徵重要性劃分這種思想,對原始特徵進行高維特徵組合,減少人工的特徵組合。
在這裏插入圖片描述
如上圖所示假設有TT個樹,不妨設T=2T15,T23T=2,T_1有5個葉子節點,T_2有3個葉子節點,樣本XiX_i在第一棵樹中被劃分到節點2,在第二棵樹中被劃分到節點3,則通過GBDT後的:transformed feature=[0,1,0,0,0,0,1,0]transformed\ feature=[0,1,0,0,0,0,1,0]
之後再結合transformed featuretransformed\ featureLMLM模型獲得預測結果。

from sklearn.ensemble.gradient_boosting import GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.preprocessing import OneHotEncoder
from xgboost import XGBClassifier
import numpy as np
from lightgbm import LGBMClassifier
from sklearn.model_selection import train_test_split


#gbdt.apply(x_test)
#返回值爲 dimension[n,n_estimator,n_leaves_index]
#假設gbdt.apply(x_test)返回值[[1,1,2],[2,2,2],[3,1,3],[4,5,4]]
#其中[1,1,2],[2,2,2],[3,1,3],[4,5,4]。表示第1,2,3,4個樣本,[1,1,2]表示樣本1,被劃分到第一棵樹的第一個葉子節點,被劃分到第二棵樹的第一個葉子節點,被劃分到第3棵樹的第二個葉子節點。
#通過OneHotEncoder()之後可以獲得每個樹的葉子節點的特徵組合。
# oneHot=OneHotEncoder()
# oneHot.fit([[1,1,2],[2,2,2],[3,1,3],[4,5,4]])
# print(oneHot.transform([[1,1,2],[2,2,2],[3,1,3],[4,5,4]]).toarray())
#[[1. 0. 0. 0. 1. 0. 0. 1. 0. 0.]
# [0. 1. 0. 0. 0. 1. 0. 1. 0. 0.]
# [0. 0. 1. 0. 1. 0. 0. 0. 1. 0.]
# [0. 0. 0. 1. 0. 0. 1. 0. 0. 1.]]



random_seed=10
class GBDT_LR():
    def __init__(self,data,label,gbdt_name):
        self.gbdt_set=['xgboost','gbdt','lgb']
        self.gbdt_name=gbdt_name
        self.data=data
        self.label=label
        self.x_train,self.x_test,self.y_train,self.y_test=train_test_split(self.data,self.label,train_size=0.7,random_state=random_seed)
        self.gbdt=self.init_gbdt()

    def init_gbdt(self):
        if self.gbdt_name == 'xgboost':
            gbdt = XGBClassifier()
        elif self.gbdt_name=='gbdt':
            gbdt=GradientBoostingClassifier()
        elif self.gbdt_name=='lgb':
            gbdt=LGBMClassifier()
        else:
            print('no valid gbdt model')
        return gbdt

    def gbdt_train(self):
        self.gbdt.fit(self.x_train,self.y_train)
    def gbdt_predict(self):
        self.gbdt_predict =self.gbdt.predict_proba(self.x_test)
    def cal_auc(self):
        gbdt_auc=roc_auc_score(self.y_test,self.gbdt_predict)

    def LR(self):
        gbdt_encoder=OneHotEncoder()
        self.lr=LogisticRegression()
        self.x_train_leafs=self.gbdt.apply(self.x_train)
        self.x_test_leafs=self.gbdt.apply(self.x_test)
        gbdt_encoder.fit(self.x_train_leafs)
        x_train_encoder=gbdt_encoder.transform(self.x_train_leafs)
        x_test_encoder = gbdt_encoder.transform(self.x_test_leafs)
        self.lr.fit(x_train_encoder,self.y_train)
        self.gbdt_lr_predict=self.lr.predict_proba(x_test_encoder)
        gbdt_lr_auc=roc_auc_score(self.y_test,self.gbdt_lr_predict)
        print('基於gbdt編碼後的LR AUC值:{:.2f}'.format(gbdt_lr_auc))
        lr2=LogisticRegression()
        lr2.fit(self.x_train)
        lr_predict=lr2.predict_proba(self.x_test)
        lr_auc=roc_auc_score(self.x_test,lr_predict)
        print('LR AUC值:{:.2f}'.format(lr_auc))

LR+DNN

Wide and deep

廣度模型(FM,FFM)一般只能學習一階和二階的特徵組合,不能很好的發現特徵之間的更加高維和抽象的關係。
深度模型(FNN/DNN)一般學習的是不可視的高階特徵的組合,但是丟失了低階特徵的信息。
wide and deep模型結合廣度模型和深度模型的優點,充分利用特徵的低階組合和高階組合,能夠達到單一模型達不到的精度。
wide and deep 模型有兩個重要的概念:
memorization :通過一系列人工的特徵叉乘(cross-product)來構造這些非線性特徵,捕捉sparse特徵之間的低階(因爲一般只做2階的,更高階難以計算)相關性,即“記憶” 歷史數據中曾共同出現過的特徵對。
優點:模型可解釋強,實現高效,特徵重要度易於分析。
缺點:需要人工的特徵工程,無法捕捉未出現過的特徵對,過高階的特徵叉乘容易出現過擬合。
generalization :Generalization 爲sparse特徵學習低維的dense embeddings來捕獲特徵相關性,學習到的embeddings本身帶有一定的語義信息。可以聯想到NLP中的詞向量,不同詞的詞向量有相關性,因此文中也稱Generalization是基於相關性之間的傳遞。這類模型的代表是DNN和FM。

Memorization趨向於更加保守,Memorization根據歷史行爲數據,產生的推薦通常和用戶已有行爲的物品直接相關的物品。而Generalization會學習新的特徵組合,提高推薦物品的多樣性。

這個是從人類的認知學習過程中演化來的。人類的大腦很複雜,它可以記憶(memorize)下每天發生的事情(麻雀可以飛,鴿子可以飛)然後泛化(generalize)這些知識到之前沒有看到過的東西(有翅膀的動物都能飛)。但是泛化的規則有時候不是特別的準,有時候會出錯(有翅膀的動物都能飛嗎)。那怎麼辦那,沒關係,記憶(memorization)可以修正泛化的規則(generalized rules),叫做特例(企鵝有翅膀,但是不能飛)。
Wide&Deep Mode就是希望計算機可以像人腦一樣,可以同時發揮memorization和generalization的作用。–Heng-Tze Cheng(Wide&Deep作者)
link

deep and wide
如上圖所示:wide and deep模型仍然存在問題,需要人工的特徵工程爲wide部分選取合適的特徵。

DeepFM

在這裏插入圖片描述
DeepFM 方法基於wide and deep思想,解決了wide and deep中需要進行人爲的特徵工程的問題。
deepfm結合FM中對每一位特徵構建一個隱向量VV的思想,對onehot後稀疏特徵進行embedding。
y^=sigmod(yFM+yDNN)\hat{y}=sigmod(y_{FM}+y_{DNN})
由上式可知 DeepFM 可以分爲 FMDNN 兩個模塊,下面分開來將。
在講兩個模塊之前,必須得清楚 DeepFM sparse feature 和 dense embeddings是什麼。

在這裏插入圖片描述
由上圖,每個xfield,ix_{field,i}都是隻有某一個index的位置爲1的稀疏向量。每個特徵xjx_j都蘊含一個隱向量VjV_j,上圖中的dense embeddings層就是每一個非零特徵xjx_j對應的隱向量VjV_j,由於一個field只有一個index爲1,其餘都爲零,因此dense embeddings節點個數kfieldk*field。這是因爲在FM中,只有當特徵xjx_j不爲0時,對應的隱向量VjV_j纔會得到調整。上圖中[V1,1,V1,2,V1,3,V1,k][V_{1,1},V_{1,2},V_{1,3},V_{1,k}]表示field 1中的某一位不爲0的特徵的隱向量。不妨設filedifiled_i的隱向量矩陣爲WtikiW^i_{t_ik},其中tit_i表示filedifiled_i中的特徵的個數,kk表示隱向量的維數。,不妨設Xfiledi,j=1X_{filed_i,j}=1,則XfilediWtiki=[Vti,1,Vti,2,Vti,3,Vti,k]X_{filed_i}*W^i_{t_ik}=[V_{t_i,1},V_{t_i,2},V_{t_i,3},V_{t_i,k}]

FM 模塊:
在這裏插入圖片描述
FM 部分可回顧章節 FM 部分。
DNN DNN部分直接使用dense embeddings作爲輸入層
a0=[e1,e2...em] a^0=[e_1,e_2...e_m]
eie_i表示等ii個filed的embedding,mm表示field的個數。

DeepFM 總結:
1.不需要任何的預訓練
2.結合wide and deep思想,可以同時具備高階和低階的特徵組合
3.使用了共享隱向量embedding的思想,避免了人爲的特徵工程。


  1. https://blog.csdn.net/Dby_freedom/article/details/89814644 ↩︎

  2. https://blog.csdn.net/haoso2/article/details/85290720 ↩︎

  3. 中心極限定理 ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章