1. 程式人生 > >推薦系統遇上深度學習(二)--FFM模型理論和實踐

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

全文共1979字,6張圖,預計閱讀時間12分鐘。

FFM理論

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

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

640?wx_fmt=png

在上面的廣告點選案例中,“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敏感特性,可以匯出其模型方程。

640?wx_fmt=jpeg

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

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

640?wx_fmt=jpeg

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

640?wx_fmt=jpeg

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

640?wx_fmt=png

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

FFM實現細節

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

損失函式

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

640?wx_fmt=png

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

隨機梯度下降

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

640?wx_fmt=jpeg

總給人一種怪怪的感覺。batch為什麼是全量的資料呢,哈哈。

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

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

640?wx_fmt=jpeg

參考文章

參考文章

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://mp.weixin.qq.com/s/Mp1eYfxbhUTZcJs_3cwQNg

查閱更為簡潔方便的分類文章以及最新的課程、產品資訊,請移步至全新呈現的“LeadAI學院官網”:

www.leadai.org

請關注人工智慧LeadAI公眾號,檢視更多專業文章

640?wx_fmt=jpeg

大家都在看

640.png?