推薦系統遇上深度學習


推薦系統遇上深度學習(一)--FM模型理論和實踐

 https://www.jianshu.com/p/152ae633fb00

1、FM背景

在計算廣告和推薦系統中,CTR預估(click-through rate)是非常重要的一個環節,判斷一個商品的是否進行推薦需要根據CTR預估的點擊率來進行。在進行CTR預估時,除了單特征外,往往要對特征進行組合。對於特征組合來說,業界現在通用的做法主要有兩大類:FM系列與Tree系列。今天,我們就來講講FM算法。

2、one-hot編碼帶來的問題

FM(Factorization Machine)主要是為了解決數據稀疏的情況下,特征怎樣組合的問題。已一個廣告分類的問題為例,根據用戶與廣告位的一些特征,來預測用戶是否會點擊廣告。數據如下:(本例來自美團技術團隊分享的paper)

 
 

clicked是分類值,表明用戶有沒有點擊該廣告。1表示點擊,0表示未點擊。而country,day,ad_type則是對應的特征。對於這種categorical特征,一般都是進行one-hot編碼處理。

將上面的數據進行one-hot編碼以后,就變成了下面這樣 :

 
 

因為是categorical特征,所以經過one-hot編碼以后,不可避免的樣本的數據就變得很稀疏。舉個非常簡單的例子,假設淘寶或者京東上的item為100萬,如果對item這個維度進行one-hot編碼,光這一個維度數據的稀疏度就是百萬分之一。由此可見,數據的稀疏性,是我們在實際應用場景中面臨的一個非常常見的挑戰與問題。

one-hot編碼帶來的另一個問題是特征空間變大。同樣以上面淘寶上的item為例,將item進行one-hot編碼以后,樣本空間有一個categorical變為了百萬維的數值特征,特征空間一下子暴增一百萬。所以大廠動不動上億維度,就是這么來的。

3、對特征進行組合

普通的線性模型,我們都是將各個特征獨立考慮的,並沒有考慮到特征與特征之間的相互關系。但實際上,大量的特征之間是有關聯的。最簡單的以電商為例,一般女性用戶看化妝品服裝之類的廣告比較多,而男性更青睞各種球類裝備。那很明顯,女性這個特征與化妝品類服裝類商品有很大的關聯性,男性這個特征與球類裝備的關聯性更為密切。如果我們能將這些有關聯的特征找出來,顯然是很有意義的。

一般的線性模型為:

 
 

從上面的式子很容易看出,一般的線性模型壓根沒有考慮特征間的關聯。為了表述特征間的相關性,我們采用多項式模型。在多項式模型中,特征xi與xj的組合用xixj表示。為了簡單起見,我們討論二階多項式模型。具體的模型表達式如下:

 
 

上式中,n表示樣本的特征數量,xi表示第i個特征。
與線性模型相比,FM的模型就多了后面特征組合的部分。

4、FM求解

從上面的式子可以很容易看出,組合部分的特征相關參數共有n(n−1)/2個。但是如第二部分所分析,在數據很稀疏的情況下,滿足xi,xj都不為0的情況非常少,這樣將導致ωij無法通過訓練得出。

為了求出ωij,我們對每一個特征分量xi引入輔助向量Vi=(vi1,vi2,⋯,vik)。然后,利用vivj^T對ωij進行求解。

 
 

那么ωij組成的矩陣可以表示為:

 
 

那么,如何求解vi和vj呢?主要采用了公式:

 
 

具體過程如下:

 
 

上面的式子中有同學曾經問我第一步是怎么推導的,其實也不難,看下面的手寫過程(大伙可不要嫌棄字丑喲)

 
 

經過這樣的分解之后,我們就可以通過隨機梯度下降SGD進行求解:

 
 

5、tensorflow代碼詳解

代碼參考地址:https://github.com/babakx/fm_tensorflow/blob/master/fm_tensorflow.ipynb
上面的代碼使用的是python2編碼,在python3下運行會出錯,所以如果大家使用的是python3的話,可以參考我寫的,其實就是修復了幾個bug啦,哈哈。
我的github地址:
https://github.com/princewen/tensorflow_practice/tree/master/recommendation-FM-demo

本文使用的數據是MovieLens100k Datase,數據包括四列,分別是用戶ID,電影ID,打分,時間。

 
 

輸入變換

要使用FM模型,我們首先要將數據處理成一個矩陣,矩陣的大小是用戶數 * 電影數。如何根據現有的數據進行處理呢?使用的是scipy.sparse中的csr.csr_matrix,理解這個函數真的費了不少功夫呢,不過還是在下面博客(https://blog.csdn.net/u012871493/article/details/51593451)的幫助下理解了函數的原理。盜用博客中的一張圖來幫助大家理解這個函數的輸入:

 
 

函數形式如下:

csr_matrix((data, indices, indptr)

可以看到,函數接收三個參數,第一個參數是數值,第二個參數是每個數對應的列號,第三個參數是每行的起始的偏移量,舉上圖的例子來說,第0行的起始偏移是0,第0行有2個非0值,因此第一行的起始偏移是2,第1行有兩個非0值,因此第二行的起始偏移是4,依次類推。

下面的代碼是如何將原始的文件輸入轉換成我們的矩陣:

def vectorize_dic(dic,ix=None,p=None,n=0,g=0): """ dic -- dictionary of feature lists. Keys are the name of features ix -- index generator (default None) p -- dimension of featrure space (number of columns in the sparse matrix) (default None) """ if ix==None: ix = dict() nz = n * g col_ix = np.empty(nz,dtype = int) i = 0 for k,lis in dic.items(): for t in range(len(lis)): ix[str(lis[t]) + str(k)] = ix.get(str(lis[t]) + str(k),0) + 1 col_ix[i+t*g] = ix[str(lis[t]) + str(k)] i += 1 row_ix = np.repeat(np.arange(0,n),g) data = np.ones(nz) if p == None: p = len(ix) ixx = np.where(col_ix < p) return csr.csr_matrix((data[ixx],(row_ix[ixx],col_ix[ixx])),shape=(n,p)),ix cols = ['user','item','rating','timestamp'] train = pd.read_csv('data/ua.base',delimiter='\t',names = cols) test = pd.read_csv('data/ua.test',delimiter='\t',names = cols) x_train,ix = vectorize_dic({'users':train['user'].values, 'items':train['item'].values},n=len(train.index),g=2) x_test,ix = vectorize_dic({'users':test['user'].values, 'items':test['item'].values},ix,x_train.shape[1],n=len(test.index),g=2) y_train = train['rating'].values y_test = test['rating'].values x_train = x_train.todense() x_test = x_test.todense() 

如果不做處理,函數返回的矩陣是按如下的格式保存的:

 
 

使用todense變換后,變成如下樣式:

 
 

估計值計算
得到我們的輸入之后,我們使用tensorflow來設計我們的模型,其實很簡單啦,我們模型的估計值由兩部分構成,原始的可以理解為線性回歸的部分,以及交叉特征的部分,交叉特征直接使用我們最后推導的形式即可,再回顧一遍:

 
 

因此,我們需要定義三個placeholder,分別是輸入的x,輸入的y,以及我們的 用戶數*電影數大小的待學習的fm矩陣:

n,p = x_train.shape

k = 10 x = tf.placeholder('float',[None,p]) y = tf.placeholder('float',[None,1]) w0 = tf.Variable(tf.zeros([1])) w = tf.Variable(tf.zeros([p])) v = tf.Variable(tf.random_normal([k,p],mean=0,stddev=0.01)) #y_hat = tf.Variable(tf.zeros([n,1])) linear_terms = tf.add(w0,tf.reduce_sum(tf.multiply(w,x),1,keep_dims=True)) # n * 1 pair_interactions = 0.5 * tf.reduce_sum( tf.subtract( tf.pow( tf.matmul(x,tf.transpose(v)),2), tf.matmul(tf.pow(x,2),tf.transpose(tf.pow(v,2))) ),axis = 1 , keep_dims=True) y_hat = tf.add(linear_terms,pair_interactions) 

定義損失函數

這里我們定義的損失函數除了平方損失外,還加了l2正則項,並使用梯度下降法進行參數的更新:

lambda_w = tf.constant(0.001,name='lambda_w') lambda_v = tf.constant(0.001,name='lambda_v') l2_norm = tf.reduce_sum( tf.add( tf.multiply(lambda_w,tf.pow(w,2)), tf.multiply(lambda_v,tf.pow(v,2)) ) ) error = tf.reduce_mean(tf.square(y-y_hat)) loss = tf.add(error,l2_norm) train_op = tf.train.GradientDescentOptimizer(learning_rate=0.01).minimize(loss) 

模型訓練
接下來就是訓練啦,這段代碼比較好理解:

epochs = 10 batch_size = 1000 # Launch the graph init = tf.global_variables_initializer() with tf.Session() as sess: sess.run(init) for epoch in tqdm(range(epochs), unit='epoch'): perm = np.random.permutation(x_train.shape[0]) # iterate over batches for bX, bY in batcher(x_train[perm], y_train[perm], batch_size): _,t = sess.run([train_op,loss], feed_dict={x: bX.reshape(-1, p), y: bY.reshape(-1, 1)}) print(t) errors = [] for bX, bY in batcher(x_test, y_test): errors.append(sess.run(error, feed_dict={x: bX.reshape(-1, p), y: bY.reshape(-1, 1)})) print(errors) RMSE = np.sqrt(np.array(errors).mean()) print (RMSE) 

參考文章:

1、http://blog.csdn.net/bitcarmanlee/article/details/52143909
2、https://blog.csdn.net/u012871493/article/details/51593451

 


作者:石曉文的學習日記
鏈接:https://www.jianshu.com/p/152ae633fb00
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。
 

推薦系統遇上深度學習(二)--FFM模型理論和實踐

https://www.jianshu.com/p/781cde3d5f3d

1、FFM理論

在CTR預估中,經常會遇到one-hot類型的變量,one-hot類型變量會導致嚴重的數據特征稀疏的情況,為了解決這一問題,在上一講中,我們介紹了FM算法。這一講我們介紹一種在FM基礎上發展出來的算法-FFM(Field-aware Factorization Machine)。

FFM模型中引入了類別的概念,即field。還是拿上一講中的數據來講,先看下圖:

 
 

在上面的廣告點擊案例中,“Day=26/11/15”、“Day=1/7/14”、“Day=19/2/15”這三個特征都是代表日期的,可以放到同一個field中。同理,Country也可以放到一個field中。簡單來說,同一個categorical特征經過One-Hot編碼生成的數值特征都可以放到同一個field,包括用戶國籍,廣告類型,日期等等。

在FFM中,每一維特征 xi,針對其它特征的每一種field fj,都會學習一個隱向量 v_i,fj。因此,隱向量不僅與特征相關,也與field相關。也就是說,“Day=26/11/15”這個特征與“Country”特征和“Ad_type"特征進行關聯的時候使用不同的隱向量,這與“Country”和“Ad_type”的內在差異相符,也是FFM中“field-aware”的由來。

假設樣本的 n個特征屬於 f個field,那么FFM的二次項有 nf個隱向量。而在FM模型中,每一維特征的隱向量只有一個。FM可以看作FFM的特例,是把所有特征都歸屬到一個field時的FFM模型。根據FFM的field敏感特性,可以導出其模型方程。

 
 

可以看到,如果隱向量的長度為 k,那么FFM的二次參數有 nfk 個,遠多於FM模型的 nk個。此外,由於隱向量與field相關,FFM二次項並不能夠化簡,其預測復雜度是 O(kn^2)。

下面以一個例子簡單說明FFM的特征組合方式。輸入記錄如下:

 
 

這條記錄可以編碼成5個特征,其中“Genre=Comedy”和“Genre=Drama”屬於同一個field,“Price”是數值型,不用One-Hot編碼轉換。為了方便說明FFM的樣本格式,我們將所有的特征和對應的field映射成整數編號。

 
 

那么,FFM的組合特征有10項,如下圖所示。

 
 

其中,紅色是field編號,藍色是特征編號。

2、FFM實現細節

這里講得只是一種FFM的實現方式,並不是唯一的。

損失函數
FFM將問題定義為分類問題,使用的是logistic loss,同時加入了正則項

 
 

什么,這是logisitc loss?第一眼看到我是懵逼的,邏輯回歸的損失函數我很熟悉啊,不是長這樣的啊?其實是我目光太短淺了。邏輯回歸其實是有兩種表述方式的損失函數的,取決於你將類別定義為0和1還是1和-1。大家可以參考下下面的文章:https://www.cnblogs.com/ljygoodgoodstudydaydayup/p/6340129.html。當我們將類別設定為1和-1的時候,邏輯回歸的損失函數就是上面的樣子。

隨機梯度下降

訓練FFM使用的是隨機梯度下降方法,即每次只選一條數據進行訓練,這里還有必要補一補梯度下降的知識,梯度下降是有三種方式的,截圖取自參考文獻3:

 
 

總給人一種怪怪的感覺。batch為什么是全量的數據呢,哈哈。

3、tensorflow實現代碼

本文代碼的github地址:
https://github.com/princewen/tensorflow_practice/tree/master/recommendation-FFM-Demo

這里我們只講解一些細節,具體的代碼大家可以去github上看:

生成數據
這里我沒有找到合適的數據,就自己產生了一點數據,數據涉及20維特征,前十維特征是一個field,后十維是一個field:

def gen_data(): labels = [-1,1] y = [np.random.choice(labels,1)[0] for _ in range(all_data_size)] x_field = [i // 10 for i in range(input_x_size)] x = np.random.randint(0,2,size=(all_data_size,input_x_size)) return x,y,x_field 

定義權重項
在ffm中,有三個權重項,首先是bias,然后是一維特征的權重,最后是交叉特征的權重:

def createTwoDimensionWeight(input_x_size,field_size,vector_dimension): weights = tf.truncated_normal([input_x_size,field_size,vector_dimension]) tf_weights = tf.Variable(weights) return tf_weights def createOneDimensionWeight(input_x_size): weights = tf.truncated_normal([input_x_size]) tf_weights = tf.Variable(weights) return tf_weights def createZeroDimensionWeight(): weights = tf.truncated_normal([1]) tf_weights = tf.Variable(weights) return tf_weights 

計算估計值
估計值的計算這里不能項FM一樣先將公式化簡再來做,對於交叉特征,只能寫兩重循環,所以對於特別多的特征的情況下,真的計算要爆炸呀!

def inference(input_x,input_x_field,zeroWeights,oneDimWeights,thirdWeight): """計算回歸模型輸出的值""" secondValue = tf.reduce_sum(tf.multiply(oneDimWeights,input_x,name='secondValue')) firstTwoValue = tf.add(zeroWeights, secondValue, name="firstTwoValue") thirdValue = tf.Variable(0.0,dtype=tf.float32) input_shape = input_x_size for i in range(input_shape): featureIndex1 = I fieldIndex1 = int(input_x_field[I]) for j in range(i+1,input_shape): featureIndex2 = j fieldIndex2 = int(input_x_field[j]) vectorLeft = tf.convert_to_tensor([[featureIndex1,fieldIndex2,i] for i in range(vector_dimension)]) weightLeft = tf.gather_nd(thirdWeight,vectorLeft) weightLeftAfterCut = tf.squeeze(weightLeft) vectorRight = tf.convert_to_tensor([[featureIndex2,fieldIndex1,i] for i in range(vector_dimension)]) weightRight = tf.gather_nd(thirdWeight,vectorRight) weightRightAfterCut = tf.squeeze(weightRight) tempValue = tf.reduce_sum(tf.multiply(weightLeftAfterCut,weightRightAfterCut)) indices2 = [I] indices3 = [j] xi = tf.squeeze(tf.gather_nd(input_x, indices2)) xj = tf.squeeze(tf.gather_nd(input_x, indices3)) product = tf.reduce_sum(tf.multiply(xi, xj)) secondItemVal = tf.multiply(tempValue, product) tf.assign(thirdValue, tf.add(thirdValue, secondItemVal)) return tf.add(firstTwoValue,thirdValue) 

定義損失函數
損失函數我們就用邏輯回歸損失函數來算,同時加入正則項:

lambda_w = tf.constant(0.001, name='lambda_w') lambda_v = tf.constant(0.001, name='lambda_v') zeroWeights = createZeroDimensionWeight() oneDimWeights = createOneDimensionWeight(input_x_size) thirdWeight = createTwoDimensionWeight(input_x_size, # 創建二次項的權重變量 field_size, vector_dimension) # n * f * k y_ = inference(input_x, trainx_field,zeroWeights,oneDimWeights,thirdWeight) l2_norm = tf.reduce_sum( tf.add( tf.multiply(lambda_w, tf.pow(oneDimWeights, 2)), tf.reduce_sum(tf.multiply(lambda_v, tf.pow(thirdWeight, 2)),axis=[1,2]) ) ) loss = tf.log(1 + tf.exp(input_y * y_)) + l2_norm train_step = tf.train.GradientDescentOptimizer(learning_rate=lr).minimize(loss) 

訓練
接下來就是訓練了,每次只用喂一個數據就好:

input_x_batch = trainx[t]
input_y_batch = trainy[t]
predict_loss,_, steps = sess.run([loss,train_step, global_step],
                         feed_dict={input_x: input_x_batch, input_y: input_y_batch})

跑的是相當的慢,我們來看看效果吧:

 
 

參考文章

1、https://tech.meituan.com/deep-understanding-of-ffm-principles-and-practices.html
2、https://www.cnblogs.com/ljygoodgoodstudydaydayup/p/6340129.html
3、https://www.cnblogs.com/pinard/p/5970503.html



作者:石曉文的學習日記
鏈接:https://www.jianshu.com/p/781cde3d5f3d
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。
 
 

推薦系統遇上深度學習(三)--DeepFM模型理論和實踐

https://www.jianshu.com/p/6f1c2643d31b

1、背景

特征組合的挑戰
對於一個基於CTR預估的推薦系統,最重要的是學習到用戶點擊行為背后隱含的特征組合。在不同的推薦場景中,低階組合特征或者高階組合特征可能都會對最終的CTR產生影響。

之前介紹的因子分解機(Factorization Machines, FM)通過對於每一維特征的隱變量內積來提取特征組合。最終的結果也非常好。但是,雖然理論上來講FM可以對高階特征組合進行建模,但實際上因為計算復雜度的原因一般都只用到了二階特征組合。

那么對於高階的特征組合來說,我們很自然的想法,通過多層的神經網絡即DNN去解決。

DNN的局限
下面的圖片來自於張俊林教授在AI大會上所使用的PPT。

我們之前也介紹過了,對於離散特征的處理,我們使用的是將特征轉換成為one-hot的形式,但是將One-hot類型的特征輸入到DNN中,會導致網絡參數太多:

 
 

如何解決這個問題呢,類似於FFM中的思想,將特征分為不同的field:

 
 

再加兩層的全鏈接層,讓Dense Vector進行組合,那么高階特征的組合就出來了

 
 

但是低階和高階特征組合隱含地體現在隱藏層中,如果我們希望把低階特征組合單獨建模,然后融合高階特征組合。

 
 

即將DNN與FM進行一個合理的融合:

 
 

二者的融合總的來說有兩種形式,一是串行結構,二是並行結構

 
 
 
 

而我們今天要講到的DeepFM,就是並行結構中的一種典型代表。

2、DeepFM模型

我們先來看一下DeepFM的模型結構:

 
 

DeepFM包含兩部分:神經網絡部分與因子分解機部分,分別負責低階特征的提取和高階特征的提取。這兩部分共享同樣的輸入。DeepFM的預測結果可以寫為:

 
 

FM部分

FM部分的詳細結構如下:

 
 

FM部分是一個因子分解機。關於因子分解機可以參閱文章[Rendle, 2010] Steffen Rendle. Factorization machines. In ICDM, 2010.。因為引入了隱變量的原因,對於幾乎不出現或者很少出現的隱變量,FM也可以很好的學習。

FM的輸出公式為:

 
 

深度部分

 
 

深度部分是一個前饋神經網絡。與圖像或者語音這類輸入不同,圖像語音的輸入一般是連續而且密集的,然而用於CTR的輸入一般是及其稀疏的。因此需要重新設計網絡結構。具體實現中為,在第一層隱含層之前,引入一個嵌入層來完成將輸入向量壓縮到低維稠密向量。

 
 

嵌入層(embedding layer)的結構如上圖所示。當前網絡結構有兩個有趣的特性,1)盡管不同field的輸入長度不同,但是embedding之后向量的長度均為K。2)在FM里得到的隱變量Vik現在作為了嵌入層網絡的權重。

這里的第二點如何理解呢,假設我們的k=5,首先,對於輸入的一條記錄,同一個field 只有一個位置是1,那么在由輸入得到dense vector的過程中,輸入層只有一個神經元起作用,得到的dense vector其實就是輸入層到embedding層該神經元相連的五條線的權重,即vi1,vi2,vi3,vi4,vi5。這五個值組合起來就是我們在FM中所提到的Vi。在FM部分和DNN部分,這一塊是共享權重的,對同一個特征來說,得到的Vi是相同的。

有關模型具體如何操作,我們可以通過代碼來進一步加深認識。

3、相關知識

我們先來講兩個代碼中會用到的相關知識吧,代碼是參考的github上星數最多的DeepFM實現代碼。

Gini Normalization
代碼中將CTR預估問題設定為一個二分類問題,繪制了Gini Normalization來評價不同模型的效果。這個是什么東西,不太懂,百度了很多,發現了一個比較通俗易懂的介紹。

假設我們有下面兩組結果,分別表示預測值和實際值:

predictions = [0.9, 0.3, 0.8, 0.75, 0.65, 0.6, 0.78, 0.7, 0.05, 0.4, 0.4, 0.05, 0.5, 0.1, 0.1]
actual = [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]

然后我們將預測值按照從小到大排列,並根據索引序對實際值進行排序:

Sorted Actual Values [0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1] 

然后,我們可以畫出如下的圖片:

 
 

接下來我們將數據Normalization到0,1之間。並畫出45度線。

 
 

橙色區域的面積,就是我們得到的Normalization的Gini系數。

這里,由於我們是將預測概率從小到大排的,所以我們希望實際值中的0盡可能出現在前面,因此Normalization的Gini系數越大,分類效果越好。

embedding_lookup
在tensorflow中有個embedding_lookup函數,我們可以直接根據一個序號來得到一個詞或者一個特征的embedding值,那么他內部其實是包含一個網絡結構的,如下圖所示:

 
 

假設我們想要找到2的embedding值,這個值其實是輸入層第二個神經元與embedding層連線的權重值。

之前有大佬跟我探討word2vec輸入的問題,現在也算是有個比較明確的答案,輸入其實就是one-hot Embedding,而word2vec要學習的是new Embedding。

4、代碼解析

好,一貫的風格,先來介紹幾個地址:
原代碼地址:https://github.com/ChenglongChen/tensorflow-DeepFM
本文代碼地址:https://github.com/princewen/tensorflow_practice/tree/master/Basic-DeepFM-model
數據下載地址:https://www.kaggle.com/c/porto-seguro-safe-driver-prediction

好了,話不多說,我們來看看代碼目錄吧,接下來,我們將主要對網絡的構建進行介紹,而對數據的處理,流程的控制部分,相信大家根據代碼就可以看懂。

項目結構
項目結構如下:

 
 

其實還應該有一個存放data的路徑。config.py保存了我們模型的一些配置。DataReader對數據進行處理,得到模型可以使用的輸入。DeepFM是我們構建的模型。main是項目的入口。metrics是計算normalized gini系數的代碼。

模型輸入

模型的輸入主要有下面幾個部分:

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.label = tf.placeholder(tf.float32,shape=[None,1],name='label') self.dropout_keep_fm = tf.placeholder(tf.float32,shape=[None],name='dropout_keep_fm') self.dropout_keep_deep = tf.placeholder(tf.float32,shape=[None],name='dropout_deep_deep') 

feat_index是特征的一個序號,主要用於通過embedding_lookup選擇我們的embedding。feat_value是對應的特征值,如果是離散特征的話,就是1,如果不是離散特征的話,就保留原來的特征值。label是實際值。還定義了兩個dropout來防止過擬合。

權重構建
權重的設定主要有兩部分,第一部分是從輸入到embedding中的權重,其實也就是我們的dense vector。另一部分就是深度神經網絡每一層的權重。第二部分很好理解,我們主要來看看第一部分:

#embeddings weights['feature_embeddings'] = tf.Variable( tf.random_normal([self.feature_size,self.embedding_size],0.0,0.01), name='feature_embeddings') weights['feature_bias'] = tf.Variable(tf.random_normal([self.feature_size,1],0.0,1.0),name='feature_bias') 

weights['feature_embeddings'] 存放的每一個值其實就是FM中的vik,所以它是F * K的。其中,F代表feture的大小(將離散特征轉換成one-hot之后的特征總量),K代表dense vector的大小。

weights['feature_bias']是FM中的一次項的權重。

Embedding part
這個部分很簡單啦,是根據feat_index選擇對應的weights['feature_embeddings']中的embedding值,然后再與對應的feat_value相乘就可以了:

# 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) 

FM part
首先來回顧一下我們之前對FM的化簡公式,之前去今日頭條面試還問到過公式的推導。

 
 

所以我們的二次項可以根據化簡公式輕松的得到,再加上我們的一次項,FM的part就算完了。同時更為方便的是,由於權重共享,我們這里可以直接用Embedding part計算出的embeddings來得到我們的二次項:

# first order term self.y_first_order = tf.nn.embedding_lookup(self.weights['feature_bias'],self.feat_index) self.y_first_order = tf.reduce_sum(tf.multiply(self.y_first_order,feat_value),2) self.y_first_order = tf.nn.dropout(self.y_first_order,self.dropout_keep_fm[0]) # second order term # sum-square-part self.summed_features_emb = tf.reduce_sum(self.embeddings,1) # None * k self.summed_features_emb_square = tf.square(self.summed_features_emb) # None * K # squre-sum-part self.squared_features_emb = tf.square(self.embeddings) self.squared_sum_features_emb = tf.reduce_sum(self.squared_features_emb, 1) # None * K #second order self.y_second_order = 0.5 * tf.subtract(self.summed_features_emb_square,self.squared_sum_features_emb) self.y_second_order = tf.nn.dropout(self.y_second_order,self.dropout_keep_fm[1]) 

DNN part
DNNpart的話,就是將Embedding part的輸出再經過幾層全鏈接層:

# Deep component self.y_deep = tf.reshape(self.embeddings,shape=[-1,self.field_size * self.embedding_size]) self.y_deep = tf.nn.dropout(self.y_deep,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["layer_%d" %i]), self.weights["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]) 

最后,我們要將DNN和FM兩部分的輸出進行結合:

concat_input = tf.concat([self.y_first_order, self.y_second_order, self.y_deep], axis=1) 

損失及優化器
我們可以使用logloss(如果定義為分類問題),或者mse(如果定義為預測問題),以及多種的優化器去進行嘗試,這些根據不同的參數設定得到:

# 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"]) if self.use_deep: for i in range(len(self.deep_layers)): self.loss += tf.contrib.layers.l2_regularizer( self.l2_reg)(self.weights["layer_%d" % I]) if self.optimizer_type == "adam": self.optimizer = tf.train.AdamOptimizer(learning_rate=self.learning_rate, beta1=0.9, beta2=0.999, epsilon=1e-8).minimize(self.loss) elif self.optimizer_type == "adagrad": self.optimizer = tf.train.AdagradOptimizer(learning_rate=self.learning_rate, initial_accumulator_value=1e-8).minimize(self.loss) elif self.optimizer_type == "gd": self.optimizer = tf.train.GradientDescentOptimizer(learning_rate=self.learning_rate).minimize(self.loss) elif self.optimizer_type == "momentum": self.optimizer = tf.train.MomentumOptimizer(learning_rate=self.learning_rate, momentum=0.95).minimize( self.loss) 

模型效果
前面提到了,我們用logloss作為損失函數去進行模型的參數更新,但是代碼中輸出了模型的 Normalization 的 Gini值來進行模型評價,我們可以對比一下(記住,Gini值越大越好呦):

 
 

好啦,本文只是提供一個引子,有關DeepFM更多的知識大家可以更多的進行學習呦。

參考資料

1、http://www.360doc.com/content/17/0315/10/10408243_637001469.shtml
2、https://blog.csdn.net/u010665216/article/details/78528261

 


作者:石曉文的學習日記
鏈接:https://www.jianshu.com/p/6f1c2643d31b
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。


免責聲明!

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



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