推薦系統(tǒng)遇上深度學(xué)習(xí)系列:
推薦系統(tǒng)遇上深度學(xué)習(xí)(一)--FM模型理論和實(shí)踐:http://www.reibang.com/p/152ae633fb00
1胯杭、FFM理論
在CTR預(yù)估中败京,經(jīng)常會(huì)遇到one-hot類型的變量脐雪,one-hot類型變量會(huì)導(dǎo)致嚴(yán)重的數(shù)據(jù)特征稀疏的情況,為了解決這一問(wèn)題悦荒,在上一講中液斜,我們介紹了FM算法。這一講我們介紹一種在FM基礎(chǔ)上發(fā)展出來(lái)的算法-FFM(Field-aware Factorization Machine)狱杰。
FFM模型中引入了類別的概念,即field枫吧。還是拿上一講中的數(shù)據(jù)來(lái)講浦旱,先看下圖:
在上面的廣告點(diǎn)擊案例中宇色,“Day=26/11/15”九杂、“Day=1/7/14”、“Day=19/2/15”這三個(gè)特征都是代表日期的宣蠕,可以放到同一個(gè)field中例隆。同理,Country也可以放到一個(gè)field中抢蚀。簡(jiǎn)單來(lái)說(shuō)镀层,同一個(gè)categorical特征經(jīng)過(guò)One-Hot編碼生成的數(shù)值特征都可以放到同一個(gè)field,包括用戶國(guó)籍,廣告類型唱逢,日期等等吴侦。
在FFM中,每一維特征 xi坞古,針對(duì)其它特征的每一種field fj备韧,都會(huì)學(xué)習(xí)一個(gè)隱向量 v_i,fj。因此痪枫,隱向量不僅與特征相關(guān)织堂,也與field相關(guān)。也就是說(shuō)奶陈,“Day=26/11/15”這個(gè)特征與“Country”特征和“Ad_type"特征進(jìn)行關(guān)聯(lián)的時(shí)候使用不同的隱向量易阳,這與“Country”和“Ad_type”的內(nèi)在差異相符,也是FFM中“field-aware”的由來(lái)吃粒。
假設(shè)樣本的 n個(gè)特征屬于 f個(gè)field潦俺,那么FFM的二次項(xiàng)有 nf個(gè)隱向量。而在FM模型中徐勃,每一維特征的隱向量只有一個(gè)黑竞。FM可以看作FFM的特例,是把所有特征都?xì)w屬到一個(gè)field時(shí)的FFM模型疏旨。根據(jù)FFM的field敏感特性很魂,可以導(dǎo)出其模型方程。
可以看到檐涝,如果隱向量的長(zhǎng)度為 k遏匆,那么FFM的二次參數(shù)有 nfk 個(gè),遠(yuǎn)多于FM模型的 nk個(gè)谁榜。此外幅聘,由于隱向量與field相關(guān),F(xiàn)FM二次項(xiàng)并不能夠化簡(jiǎn)窃植,其預(yù)測(cè)復(fù)雜度是 O(kn^2)帝蒿。
下面以一個(gè)例子簡(jiǎn)單說(shuō)明FFM的特征組合方式。輸入記錄如下:
這條記錄可以編碼成5個(gè)特征巷怜,其中“Genre=Comedy”和“Genre=Drama”屬于同一個(gè)field葛超,“Price”是數(shù)值型,不用One-Hot編碼轉(zhuǎn)換延塑。為了方便說(shuō)明FFM的樣本格式绣张,我們將所有的特征和對(duì)應(yīng)的field映射成整數(shù)編號(hào)。
那么关带,F(xiàn)FM的組合特征有10項(xiàng)侥涵,如下圖所示。
其中,紅色是field編號(hào)芜飘,藍(lán)色是特征編號(hào)务豺。
2、FFM實(shí)現(xiàn)細(xì)節(jié)
這里講得只是一種FFM的實(shí)現(xiàn)方式嗦明,并不是唯一的冲呢。
損失函數(shù)
FFM將問(wèn)題定義為分類問(wèn)題,使用的是logistic loss招狸,同時(shí)加入了正則項(xiàng)
什么敬拓,這是logisitc loss?第一眼看到我是懵逼的裙戏,邏輯回歸的損失函數(shù)我很熟悉啊乘凸,不是長(zhǎng)這樣的啊累榜?其實(shí)是我目光太短淺了营勤。邏輯回歸其實(shí)是有兩種表述方式的損失函數(shù)的,取決于你將類別定義為0和1還是1和-1壹罚。大家可以參考下下面的文章:https://www.cnblogs.com/ljygoodgoodstudydaydayup/p/6340129.html葛作。當(dāng)我們將類別設(shè)定為1和-1的時(shí)候,邏輯回歸的損失函數(shù)就是上面的樣子猖凛。
隨機(jī)梯度下降
訓(xùn)練FFM使用的是隨機(jī)梯度下降方法赂蠢,即每次只選一條數(shù)據(jù)進(jìn)行訓(xùn)練,這里還有必要補(bǔ)一補(bǔ)梯度下降的知識(shí)辨泳,梯度下降是有三種方式的虱岂,截圖取自參考文獻(xiàn)3:
總給人一種怪怪的感覺(jué)。batch為什么是全量的數(shù)據(jù)呢菠红,哈哈第岖。
3、tensorflow實(shí)現(xiàn)代碼
本文代碼的github地址:
https://github.com/princewen/tensorflow_practice/tree/master/recommendation-FFM-Demo
這里我們只講解一些細(xì)節(jié)试溯,具體的代碼大家可以去github上看:
生成數(shù)據(jù)
這里我沒(méi)有找到合適的數(shù)據(jù)蔑滓,就自己產(chǎn)生了一點(diǎn)數(shù)據(jù),數(shù)據(jù)涉及20維特征遇绞,前十維特征是一個(gè)field键袱,后十維是一個(gè)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
定義權(quán)重項(xiàng)
在ffm中,有三個(gè)權(quán)重項(xiàng)试读,首先是bias杠纵,然后是一維特征的權(quán)重,最后是交叉特征的權(quán)重:
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
計(jì)算估計(jì)值
估計(jì)值的計(jì)算這里不能項(xiàng)FM一樣先將公式化簡(jiǎn)再來(lái)做钩骇,對(duì)于交叉特征,只能寫(xiě)兩重循環(huán),所以對(duì)于特別多的特征的情況下倘屹,真的計(jì)算要爆炸呀银亲!
def inference(input_x,input_x_field,zeroWeights,oneDimWeights,thirdWeight):
"""計(jì)算回歸模型輸出的值"""
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)
定義損失函數(shù)
損失函數(shù)我們就用邏輯回歸損失函數(shù)來(lái)算,同時(shí)加入正則項(xiàng):
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, # 創(chuàng)建二次項(xiàng)的權(quán)重變量
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)
訓(xùn)練
接下來(lái)就是訓(xùn)練了纽匙,每次只用喂一個(gè)數(shù)據(jù)就好:
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})
跑的是相當(dāng)?shù)穆耱穑覀儊?lái)看看效果吧:
參考文章
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